init commit
This commit is contained in:
59
.dockerignore
Normal file
59
.dockerignore
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Виртуальное окружение
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Кэш Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Локальные настройки и данные
|
||||||
|
.env.local
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# Git и GitHub файлы
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# IDE файлы
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# История и временные файлы
|
||||||
|
.history/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Файлы Windows
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
26
.env-example
Normal file
26
.env-example
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Telegram Bot API
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321 # ID пользователей-администраторов через запятую
|
||||||
|
|
||||||
|
# Synology NAS
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000 # Обычно 5000 для HTTP и 5001 для HTTPS
|
||||||
|
SYNOLOGY_USERNAME=your_username
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=True # Использовать HTTPS
|
||||||
|
SYNOLOGY_VERIFY_SSL=False # Проверка SSL-сертификата
|
||||||
|
SYNOLOGY_TIMEOUT=10 # Таймаут для API запросов в секундах
|
||||||
|
SYNOLOGY_API_VERSION=1 # Версия API
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.System # API для управления питанием
|
||||||
|
|
||||||
|
# WOL (Wake-on-LAN)
|
||||||
|
MAC_ADDRESS=00:11:22:33:44:55 # MAC-адрес Synology NAS
|
||||||
|
WOL_BROADCAST=255.255.255.255 # Broadcast-адрес для WOL
|
||||||
|
WOL_PORT=9 # Порт для WOL (обычно 7 или 9)
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
# Docker specific
|
||||||
|
DOCKER_ENV=true # Указывает, что приложение запущено в Docker
|
||||||
|
HEALTHCHECK_PORT=8080 # Порт для healthcheck
|
||||||
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS specific files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
58
.history/.dockerignore_20250830102713
Normal file
58
.history/.dockerignore_20250830102713
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Виртуальное окружение
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Кэш Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Локальные настройки и данные
|
||||||
|
.env.local
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# Git и GitHub файлы
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# IDE файлы
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# История и временные файлы
|
||||||
|
.history/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Файлы Windows
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
58
.history/.dockerignore_20250830103154
Normal file
58
.history/.dockerignore_20250830103154
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Виртуальное окружение
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Кэш Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Локальные настройки и данные
|
||||||
|
.env.local
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# Git и GitHub файлы
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# IDE файлы
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# История и временные файлы
|
||||||
|
.history/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Файлы Windows
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
59
.history/.dockerignore_20250830103255
Normal file
59
.history/.dockerignore_20250830103255
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Виртуальное окружение
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Кэш Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Логи
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Локальные настройки и данные
|
||||||
|
.env.local
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# Git и GitHub файлы
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# IDE файлы
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# История и временные файлы
|
||||||
|
.history/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Файлы Windows
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
$RECYCLE.BIN/
|
||||||
26
.history/.env-example_20250830103005
Normal file
26
.history/.env-example_20250830103005
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Telegram Bot API
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321 # ID пользователей-администраторов через запятую
|
||||||
|
|
||||||
|
# Synology NAS
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000 # Обычно 5000 для HTTP и 5001 для HTTPS
|
||||||
|
SYNOLOGY_USERNAME=your_username
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=True # Использовать HTTPS
|
||||||
|
SYNOLOGY_VERIFY_SSL=False # Проверка SSL-сертификата
|
||||||
|
SYNOLOGY_TIMEOUT=10 # Таймаут для API запросов в секундах
|
||||||
|
SYNOLOGY_API_VERSION=1 # Версия API
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.System # API для управления питанием
|
||||||
|
|
||||||
|
# WOL (Wake-on-LAN)
|
||||||
|
MAC_ADDRESS=00:11:22:33:44:55 # MAC-адрес Synology NAS
|
||||||
|
WOL_BROADCAST=255.255.255.255 # Broadcast-адрес для WOL
|
||||||
|
WOL_PORT=9 # Порт для WOL (обычно 7 или 9)
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
# Docker specific
|
||||||
|
DOCKER_ENV=true # Указывает, что приложение запущено в Docker
|
||||||
|
HEALTHCHECK_PORT=8080 # Порт для healthcheck
|
||||||
26
.history/.env-example_20250830103154
Normal file
26
.history/.env-example_20250830103154
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Telegram Bot API
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321 # ID пользователей-администраторов через запятую
|
||||||
|
|
||||||
|
# Synology NAS
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000 # Обычно 5000 для HTTP и 5001 для HTTPS
|
||||||
|
SYNOLOGY_USERNAME=your_username
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=True # Использовать HTTPS
|
||||||
|
SYNOLOGY_VERIFY_SSL=False # Проверка SSL-сертификата
|
||||||
|
SYNOLOGY_TIMEOUT=10 # Таймаут для API запросов в секундах
|
||||||
|
SYNOLOGY_API_VERSION=1 # Версия API
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.System # API для управления питанием
|
||||||
|
|
||||||
|
# WOL (Wake-on-LAN)
|
||||||
|
MAC_ADDRESS=00:11:22:33:44:55 # MAC-адрес Synology NAS
|
||||||
|
WOL_BROADCAST=255.255.255.255 # Broadcast-адрес для WOL
|
||||||
|
WOL_PORT=9 # Порт для WOL (обычно 7 или 9)
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
# Docker specific
|
||||||
|
DOCKER_ENV=true # Указывает, что приложение запущено в Docker
|
||||||
|
HEALTHCHECK_PORT=8080 # Порт для healthcheck
|
||||||
15
.history/.env_20250830063713
Normal file
15
.history/.env_20250830063713
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
15
.history/.env_20250830063839
Normal file
15
.history/.env_20250830063839
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
15
.history/.env_20250830071119
Normal file
15
.history/.env_20250830071119
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
15
.history/.env_20250830071139
Normal file
15
.history/.env_20250830071139
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
15
.history/.env_20250830071153
Normal file
15
.history/.env_20250830071153
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
15
.history/.env_20250830071249
Normal file
15
.history/.env_20250830071249
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
15
.history/.env_20250830071300
Normal file
15
.history/.env_20250830071300
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
16
.history/.env_20250830072439.example
Normal file
16
.history/.env_20250830072439.example
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=1
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
16
.history/.env_20250830072817.example
Normal file
16
.history/.env_20250830072817.example
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=1
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
16
.history/.env_20250830080745
Normal file
16
.history/.env_20250830080745
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=1
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
16
.history/.env_20250830082200
Normal file
16
.history/.env_20250830082200
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=2
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
20
.history/.env_20250830082210
Normal file
20
.history/.env_20250830082210
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=2
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.Hardware.PowerRecovery
|
||||||
|
SYNOLOGY_INFO_API=SYNO.DSM.Info
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
20
.history/.env_20250830082500
Normal file
20
.history/.env_20250830082500
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=2
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.Hardware.PowerRecovery
|
||||||
|
SYNOLOGY_INFO_API=SYNO.DSM.Info
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
20
.history/.env_20250830083237
Normal file
20
.history/.env_20250830083237
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=6
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.Hardware.PowerRecovery
|
||||||
|
SYNOLOGY_INFO_API=SYNO.DSM.Info
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
20
.history/.env_20250830101005
Normal file
20
.history/.env_20250830101005
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=6
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.System
|
||||||
|
SYNOLOGY_INFO_API=SYNO.DSM.Info
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
20
.history/.env_20250830101843
Normal file
20
.history/.env_20250830101843
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=879254890:AAGOVgN6yF9Xx0PXlkTVncln8RJkh3BL1AY
|
||||||
|
ADMIN_USER_IDS=556399210
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.0.102
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=superadmin
|
||||||
|
SYNOLOGY_PASSWORD=Cl0ud_1985!
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
SYNOLOGY_API_VERSION=6
|
||||||
|
|
||||||
|
# API Configuration
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.System
|
||||||
|
SYNOLOGY_INFO_API=SYNO.DSM.Info
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=90:09:D0:8C:27:F9
|
||||||
|
WOL_PORT=9
|
||||||
44
.history/.gitignore_20250830063748
Normal file
44
.history/.gitignore_20250830063748
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS specific files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
44
.history/.gitignore_20250830063839
Normal file
44
.history/.gitignore_20250830063839
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE specific files
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS specific files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
218
.history/DOCKER_DEPLOYMENT_20250830103243.md
Normal file
218
.history/DOCKER_DEPLOYMENT_20250830103243.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# Synology Power Control Bot - Руководство по развертыванию в Docker
|
||||||
|
|
||||||
|
## Подготовка к развертыванию
|
||||||
|
|
||||||
|
Это руководство поможет вам развернуть бота для управления питанием Synology NAS в Docker-контейнере. Развертывание в Docker имеет следующие преимущества:
|
||||||
|
- Изоляция приложения и его зависимостей
|
||||||
|
- Простота управления и обновления
|
||||||
|
- Автоматический перезапуск при сбоях
|
||||||
|
- Возможность легкого переноса между системами
|
||||||
|
|
||||||
|
## Предварительные требования
|
||||||
|
|
||||||
|
1. **Установка Docker и Docker Compose**:
|
||||||
|
|
||||||
|
**Для Ubuntu/Debian**:
|
||||||
|
```bash
|
||||||
|
# Установка Docker
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install apt-transport-https ca-certificates curl software-properties-common
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install docker-ce docker-ce-cli containerd.io
|
||||||
|
|
||||||
|
# Установка Docker Compose
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Для CentOS/RHEL**:
|
||||||
|
```bash
|
||||||
|
# Установка Docker
|
||||||
|
sudo yum install -y yum-utils
|
||||||
|
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||||
|
sudo yum install docker-ce docker-ce-cli containerd.io
|
||||||
|
sudo systemctl start docker
|
||||||
|
sudo systemctl enable docker
|
||||||
|
|
||||||
|
# Установка Docker Compose
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Для Windows**:
|
||||||
|
- Скачайте и установите Docker Desktop с [официального сайта Docker](https://www.docker.com/products/docker-desktop/)
|
||||||
|
|
||||||
|
2. **Настройка проекта**:
|
||||||
|
```bash
|
||||||
|
# Клонирование репозитория (если используете Git)
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
|
||||||
|
# Или распакуйте архив с исходным кодом
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
1. **Создайте файл .env**:
|
||||||
|
Создайте файл `.env` на основе `.env-example` и настройте его с вашими параметрами:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env-example .env
|
||||||
|
nano .env # или любой другой текстовый редактор
|
||||||
|
```
|
||||||
|
|
||||||
|
Заполните следующие параметры:
|
||||||
|
- `TELEGRAM_TOKEN`: Токен вашего Telegram-бота от @BotFather
|
||||||
|
- `ADMIN_USER_IDS`: ID пользователей Telegram с доступом к боту
|
||||||
|
- `SYNOLOGY_HOST`: IP-адрес вашего Synology NAS
|
||||||
|
- `SYNOLOGY_USERNAME` и `SYNOLOGY_PASSWORD`: Учетные данные для DSM
|
||||||
|
- `MAC_ADDRESS`: MAC-адрес Synology NAS для Wake-on-LAN
|
||||||
|
|
||||||
|
## Развертывание
|
||||||
|
|
||||||
|
### С использованием скриптов:
|
||||||
|
|
||||||
|
**Linux**:
|
||||||
|
```bash
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows**:
|
||||||
|
```
|
||||||
|
deploy.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Вручную с Docker Compose:
|
||||||
|
|
||||||
|
1. **Сборка и запуск**:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверка статуса**:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Просмотр логов**:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Остановка**:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Управление контейнером
|
||||||
|
|
||||||
|
### Перезапуск бота:
|
||||||
|
```bash
|
||||||
|
docker-compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Обновление:
|
||||||
|
```bash
|
||||||
|
# Остановка
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Обновление (если используете Git)
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Пересборка и запуск
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Резервное копирование данных:
|
||||||
|
Важные данные хранятся в томе `logs`, который можно скопировать:
|
||||||
|
```bash
|
||||||
|
# Создание бэкапа логов
|
||||||
|
tar -czvf synology_bot_logs_backup.tar.gz ./logs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка работоспособности
|
||||||
|
|
||||||
|
После развертывания можно проверить состояние бота с помощью следующих команд:
|
||||||
|
|
||||||
|
1. **Проверка статуса контейнера**:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверка health-check**:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
Должен вернуть `OK`.
|
||||||
|
|
||||||
|
3. **Проверка логов**:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
Ищите строки с успешной инициализацией бота.
|
||||||
|
|
||||||
|
## Решение проблем
|
||||||
|
|
||||||
|
### Контейнер не запускается или сразу завершает работу
|
||||||
|
- Проверьте логи: `docker-compose logs -f`
|
||||||
|
- Проверьте файл `.env` на наличие всех необходимых параметров
|
||||||
|
- Убедитесь, что порт 8080 не занят другим приложением
|
||||||
|
|
||||||
|
### Проблемы с подключением к Synology NAS
|
||||||
|
- Проверьте доступность NAS из контейнера:
|
||||||
|
```bash
|
||||||
|
docker-compose exec synology-bot ping $SYNOLOGY_HOST
|
||||||
|
```
|
||||||
|
- Проверьте правильность учетных данных
|
||||||
|
- Убедитесь, что API DSM включено в настройках NAS
|
||||||
|
|
||||||
|
### Telegram-бот не отвечает
|
||||||
|
- Проверьте корректность TELEGRAM_TOKEN
|
||||||
|
- Убедитесь, что бот запущен: `/start` в чате с ботом
|
||||||
|
- Проверьте, что ваш Telegram ID указан в ADMIN_USER_IDS
|
||||||
|
|
||||||
|
## Автоматический запуск при перезагрузке сервера
|
||||||
|
|
||||||
|
Docker и Docker Compose по умолчанию настроены на автоматический запуск контейнеров при перезагрузке системы благодаря параметру `restart: unless-stopped` в docker-compose.yml.
|
||||||
|
|
||||||
|
Если эта опция не работает, вы можете настроить systemd:
|
||||||
|
|
||||||
|
1. **Создайте файл сервиса**:
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/systemd/system/synology-bot.service
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Добавьте следующее содержимое**:
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=Synology Power Control Bot
|
||||||
|
Requires=docker.service
|
||||||
|
After=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
WorkingDirectory=/path/to/synology_power_control_bot
|
||||||
|
ExecStart=/usr/local/bin/docker-compose up -d
|
||||||
|
ExecStop=/usr/local/bin/docker-compose down
|
||||||
|
TimeoutStartSec=0
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Активируйте и запустите сервис**:
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable synology-bot.service
|
||||||
|
sudo systemctl start synology-bot.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- Не передавайте файл `.env` с учетными данными третьим лицам
|
||||||
|
- Регулярно меняйте пароль от DSM
|
||||||
|
- Ограничьте доступ к боту только доверенным пользователям
|
||||||
|
- Рассмотрите возможность запуска на выделенной сети или с дополнительными ограничениями доступа
|
||||||
218
.history/DOCKER_DEPLOYMENT_20250830103340.md
Normal file
218
.history/DOCKER_DEPLOYMENT_20250830103340.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
# Synology Power Control Bot - Руководство по развертыванию в Docker
|
||||||
|
|
||||||
|
## Подготовка к развертыванию
|
||||||
|
|
||||||
|
Это руководство поможет вам развернуть бота для управления питанием Synology NAS в Docker-контейнере. Развертывание в Docker имеет следующие преимущества:
|
||||||
|
- Изоляция приложения и его зависимостей
|
||||||
|
- Простота управления и обновления
|
||||||
|
- Автоматический перезапуск при сбоях
|
||||||
|
- Возможность легкого переноса между системами
|
||||||
|
|
||||||
|
## Предварительные требования
|
||||||
|
|
||||||
|
1. **Установка Docker и Docker Compose**:
|
||||||
|
|
||||||
|
**Для Ubuntu/Debian**:
|
||||||
|
```bash
|
||||||
|
# Установка Docker
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install apt-transport-https ca-certificates curl software-properties-common
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install docker-ce docker-ce-cli containerd.io
|
||||||
|
|
||||||
|
# Установка Docker Compose
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Для CentOS/RHEL**:
|
||||||
|
```bash
|
||||||
|
# Установка Docker
|
||||||
|
sudo yum install -y yum-utils
|
||||||
|
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
|
||||||
|
sudo yum install docker-ce docker-ce-cli containerd.io
|
||||||
|
sudo systemctl start docker
|
||||||
|
sudo systemctl enable docker
|
||||||
|
|
||||||
|
# Установка Docker Compose
|
||||||
|
sudo curl -L "https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||||
|
sudo chmod +x /usr/local/bin/docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
**Для Windows**:
|
||||||
|
- Скачайте и установите Docker Desktop с [официального сайта Docker](https://www.docker.com/products/docker-desktop/)
|
||||||
|
|
||||||
|
2. **Настройка проекта**:
|
||||||
|
```bash
|
||||||
|
# Клонирование репозитория (если используете Git)
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
|
||||||
|
# Или распакуйте архив с исходным кодом
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
1. **Создайте файл .env**:
|
||||||
|
Создайте файл `.env` на основе `.env-example` и настройте его с вашими параметрами:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env-example .env
|
||||||
|
nano .env # или любой другой текстовый редактор
|
||||||
|
```
|
||||||
|
|
||||||
|
Заполните следующие параметры:
|
||||||
|
- `TELEGRAM_TOKEN`: Токен вашего Telegram-бота от @BotFather
|
||||||
|
- `ADMIN_USER_IDS`: ID пользователей Telegram с доступом к боту
|
||||||
|
- `SYNOLOGY_HOST`: IP-адрес вашего Synology NAS
|
||||||
|
- `SYNOLOGY_USERNAME` и `SYNOLOGY_PASSWORD`: Учетные данные для DSM
|
||||||
|
- `MAC_ADDRESS`: MAC-адрес Synology NAS для Wake-on-LAN
|
||||||
|
|
||||||
|
## Развертывание
|
||||||
|
|
||||||
|
### С использованием скриптов:
|
||||||
|
|
||||||
|
**Linux**:
|
||||||
|
```bash
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows**:
|
||||||
|
```
|
||||||
|
deploy.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Вручную с Docker Compose:
|
||||||
|
|
||||||
|
1. **Сборка и запуск**:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверка статуса**:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Просмотр логов**:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Остановка**:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Управление контейнером
|
||||||
|
|
||||||
|
### Перезапуск бота:
|
||||||
|
```bash
|
||||||
|
docker-compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Обновление:
|
||||||
|
```bash
|
||||||
|
# Остановка
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Обновление (если используете Git)
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Пересборка и запуск
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Резервное копирование данных:
|
||||||
|
Важные данные хранятся в томе `logs`, который можно скопировать:
|
||||||
|
```bash
|
||||||
|
# Создание бэкапа логов
|
||||||
|
tar -czvf synology_bot_logs_backup.tar.gz ./logs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка работоспособности
|
||||||
|
|
||||||
|
После развертывания можно проверить состояние бота с помощью следующих команд:
|
||||||
|
|
||||||
|
1. **Проверка статуса контейнера**:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Проверка health-check**:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
Должен вернуть `OK`.
|
||||||
|
|
||||||
|
3. **Проверка логов**:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
Ищите строки с успешной инициализацией бота.
|
||||||
|
|
||||||
|
## Решение проблем
|
||||||
|
|
||||||
|
### Контейнер не запускается или сразу завершает работу
|
||||||
|
- Проверьте логи: `docker-compose logs -f`
|
||||||
|
- Проверьте файл `.env` на наличие всех необходимых параметров
|
||||||
|
- Убедитесь, что порт 8080 не занят другим приложением
|
||||||
|
|
||||||
|
### Проблемы с подключением к Synology NAS
|
||||||
|
- Проверьте доступность NAS из контейнера:
|
||||||
|
```bash
|
||||||
|
docker-compose exec synology-bot ping $SYNOLOGY_HOST
|
||||||
|
```
|
||||||
|
- Проверьте правильность учетных данных
|
||||||
|
- Убедитесь, что API DSM включено в настройках NAS
|
||||||
|
|
||||||
|
### Telegram-бот не отвечает
|
||||||
|
- Проверьте корректность TELEGRAM_TOKEN
|
||||||
|
- Убедитесь, что бот запущен: `/start` в чате с ботом
|
||||||
|
- Проверьте, что ваш Telegram ID указан в ADMIN_USER_IDS
|
||||||
|
|
||||||
|
## Автоматический запуск при перезагрузке сервера
|
||||||
|
|
||||||
|
Docker и Docker Compose по умолчанию настроены на автоматический запуск контейнеров при перезагрузке системы благодаря параметру `restart: unless-stopped` в docker-compose.yml.
|
||||||
|
|
||||||
|
Если эта опция не работает, вы можете настроить systemd:
|
||||||
|
|
||||||
|
1. **Создайте файл сервиса**:
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/systemd/system/synology-bot.service
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Добавьте следующее содержимое**:
|
||||||
|
```
|
||||||
|
[Unit]
|
||||||
|
Description=Synology Power Control Bot
|
||||||
|
Requires=docker.service
|
||||||
|
After=docker.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
WorkingDirectory=/path/to/synology_power_control_bot
|
||||||
|
ExecStart=/usr/local/bin/docker-compose up -d
|
||||||
|
ExecStop=/usr/local/bin/docker-compose down
|
||||||
|
TimeoutStartSec=0
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Активируйте и запустите сервис**:
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable synology-bot.service
|
||||||
|
sudo systemctl start synology-bot.service
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
- Не передавайте файл `.env` с учетными данными третьим лицам
|
||||||
|
- Регулярно меняйте пароль от DSM
|
||||||
|
- Ограничьте доступ к боту только доверенным пользователям
|
||||||
|
- Рассмотрите возможность запуска на выделенной сети или с дополнительными ограничениями доступа
|
||||||
20
.history/Dockerfile_20250830102631
Normal file
20
.history/Dockerfile_20250830102631
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем файлы зависимостей
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Устанавливаем зависимости
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Копируем исходный код
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Указываем переменные окружения
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Запускаем приложение
|
||||||
|
CMD ["python", "run.py"]
|
||||||
23
.history/Dockerfile_20250830102802
Normal file
23
.history/Dockerfile_20250830102802
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем файлы зависимостей
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Устанавливаем зависимости
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Копируем исходный код
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Делаем entrypoint исполняемым
|
||||||
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
|
# Указываем переменные окружения
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Используем entrypoint.sh
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
23
.history/Dockerfile_20250830103154
Normal file
23
.history/Dockerfile_20250830103154
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем файлы зависимостей
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Устанавливаем зависимости
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Копируем исходный код
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Делаем entrypoint исполняемым
|
||||||
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
|
# Указываем переменные окружения
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
# Используем entrypoint.sh
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
101
.history/README_20250830063733.md
Normal file
101
.history/README_20250830063733.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы
|
||||||
|
- ✅ Проверка статуса и получение информации о системе
|
||||||
|
- ✅ Ограничение доступа по ID пользователей
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
101
.history/README_20250830063839.md
Normal file
101
.history/README_20250830063839.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы
|
||||||
|
- ✅ Проверка статуса и получение информации о системе
|
||||||
|
- ✅ Ограничение доступа по ID пользователей
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
117
.history/README_20250830065402.md
Normal file
117
.history/README_20250830065402.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
125
.history/README_20250830065412.md
Normal file
125
.history/README_20250830065412.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
125
.history/README_20250830065454.md
Normal file
125
.history/README_20250830065454.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
126
.history/README_20250830072408.md
Normal file
126
.history/README_20250830072408.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
126
.history/README_20250830072817.md
Normal file
126
.history/README_20250830072817.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
128
.history/README_20250830092253.md
Normal file
128
.history/README_20250830092253.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
131
.history/README_20250830092310.md
Normal file
131
.history/README_20250830092310.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
146
.history/README_20250830092330.md
Normal file
146
.history/README_20250830092330.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Информационные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
- `/temperature` - Температура устройства
|
||||||
|
- `/processes` - Список активных процессов
|
||||||
|
- `/network` - Сетевая информация
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/schedule` - Расписание питания
|
||||||
|
- `/browse` - Просмотр файлов
|
||||||
|
- `/search <запрос>` - Поиск файлов
|
||||||
|
- `/updates` - Проверка обновлений
|
||||||
|
- `/backup` - Статус резервного копирования
|
||||||
|
- `/quota` - Квоты пользователей
|
||||||
|
|
||||||
|
### Быстрые команды
|
||||||
|
- `/quickreboot` - Быстрая перезагрузка
|
||||||
|
- `/wakeup` - Пробуждение NAS (WOL)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
151
.history/README_20250830092350.md
Normal file
151
.history/README_20250830092350.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
### Дополнительные функции
|
||||||
|
- ✅ Мониторинг обновлений DSM и пакетов
|
||||||
|
- ✅ Управление расписанием питания
|
||||||
|
- ✅ Проверка статуса резервного копирования
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Информационные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
- `/temperature` - Температура устройства
|
||||||
|
- `/processes` - Список активных процессов
|
||||||
|
- `/network` - Сетевая информация
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/schedule` - Расписание питания
|
||||||
|
- `/browse` - Просмотр файлов
|
||||||
|
- `/search <запрос>` - Поиск файлов
|
||||||
|
- `/updates` - Проверка обновлений
|
||||||
|
- `/backup` - Статус резервного копирования
|
||||||
|
- `/quota` - Квоты пользователей
|
||||||
|
|
||||||
|
### Быстрые команды
|
||||||
|
- `/quickreboot` - Быстрая перезагрузка
|
||||||
|
- `/wakeup` - Пробуждение NAS (WOL)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
151
.history/README_20250830092440.md
Normal file
151
.history/README_20250830092440.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
### Дополнительные функции
|
||||||
|
- ✅ Мониторинг обновлений DSM и пакетов
|
||||||
|
- ✅ Управление расписанием питания
|
||||||
|
- ✅ Проверка статуса резервного копирования
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python -m src.bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Информационные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
- `/temperature` - Температура устройства
|
||||||
|
- `/processes` - Список активных процессов
|
||||||
|
- `/network` - Сетевая информация
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/schedule` - Расписание питания
|
||||||
|
- `/browse` - Просмотр файлов
|
||||||
|
- `/search <запрос>` - Поиск файлов
|
||||||
|
- `/updates` - Проверка обновлений
|
||||||
|
- `/backup` - Статус резервного копирования
|
||||||
|
- `/quota` - Квоты пользователей
|
||||||
|
|
||||||
|
### Быстрые команды
|
||||||
|
- `/quickreboot` - Быстрая перезагрузка
|
||||||
|
- `/wakeup` - Пробуждение NAS (WOL)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
192
.history/README_20250830103059.md
Normal file
192
.history/README_20250830103059.md
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
### Дополнительные функции
|
||||||
|
- ✅ Мониторинг обновлений DSM и пакетов
|
||||||
|
- ✅ Управление расписанием питания
|
||||||
|
- ✅ Проверка статуса резервного копирования
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
### Метод 1: Локальный запуск
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод 2: Docker
|
||||||
|
|
||||||
|
1. Убедитесь, что Docker и Docker Compose установлены в вашей системе.
|
||||||
|
|
||||||
|
2. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Создайте файл `.env` на основе `.env-example` и заполните необходимыми значениями.
|
||||||
|
|
||||||
|
4. Запустите скрипт развертывания:
|
||||||
|
```bash
|
||||||
|
# Linux/macOS
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
deploy.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Или запустите вручную:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Проверьте статус:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Просмотр логов:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Дополнительная информация о Docker-развертывании доступна в файле [README_DOCKER.md](README_DOCKER.md).
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Информационные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
- `/temperature` - Температура устройства
|
||||||
|
- `/processes` - Список активных процессов
|
||||||
|
- `/network` - Сетевая информация
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/schedule` - Расписание питания
|
||||||
|
- `/browse` - Просмотр файлов
|
||||||
|
- `/search <запрос>` - Поиск файлов
|
||||||
|
- `/updates` - Проверка обновлений
|
||||||
|
- `/backup` - Статус резервного копирования
|
||||||
|
- `/quota` - Квоты пользователей
|
||||||
|
|
||||||
|
### Быстрые команды
|
||||||
|
- `/quickreboot` - Быстрая перезагрузка
|
||||||
|
- `/wakeup` - Пробуждение NAS (WOL)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
201
.history/README_20250830103119.md
Normal file
201
.history/README_20250830103119.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
### Дополнительные функции
|
||||||
|
- ✅ Мониторинг обновлений DSM и пакетов
|
||||||
|
- ✅ Управление расписанием питания
|
||||||
|
- ✅ Проверка статуса резервного копирования
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
### Метод 1: Локальный запуск
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод 2: Docker
|
||||||
|
|
||||||
|
1. Убедитесь, что Docker и Docker Compose установлены в вашей системе.
|
||||||
|
|
||||||
|
2. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Создайте файл `.env` на основе `.env-example` и заполните необходимыми значениями.
|
||||||
|
|
||||||
|
4. Запустите скрипт развертывания:
|
||||||
|
```bash
|
||||||
|
# Linux/macOS
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
deploy.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Или запустите вручную:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Проверьте статус:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Просмотр логов:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Дополнительная информация о Docker-развертывании доступна в файле [README_DOCKER.md](README_DOCKER.md).
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Информационные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
- `/temperature` - Температура устройства
|
||||||
|
- `/processes` - Список активных процессов
|
||||||
|
- `/network` - Сетевая информация
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/schedule` - Расписание питания
|
||||||
|
- `/browse` - Просмотр файлов
|
||||||
|
- `/search <запрос>` - Поиск файлов
|
||||||
|
- `/updates` - Проверка обновлений
|
||||||
|
- `/backup` - Статус резервного копирования
|
||||||
|
- `/quota` - Квоты пользователей
|
||||||
|
|
||||||
|
### Быстрые команды
|
||||||
|
- `/quickreboot` - Быстрая перезагрузка
|
||||||
|
- `/wakeup` - Пробуждение NAS (WOL)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .env-example # Пример файла переменных окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
├── .dockerignore # Файлы, игнорируемые при сборке Docker-образа
|
||||||
|
├── Dockerfile # Инструкции для сборки Docker-образа
|
||||||
|
├── docker-compose.yml # Конфигурация Docker Compose
|
||||||
|
├── deploy.sh # Скрипт развёртывания для Linux
|
||||||
|
├── deploy.cmd # Скрипт развёртывания для Windows
|
||||||
|
├── entrypoint.sh # Скрипт для запуска в Docker
|
||||||
|
├── README.md # Основная документация
|
||||||
|
├── README_DOCKER.md # Документация по Docker-развёртыванию
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
236
.history/README_20250830103139.md
Normal file
236
.history/README_20250830103139.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
### Дополнительные функции
|
||||||
|
- ✅ Мониторинг обновлений DSM и пакетов
|
||||||
|
- ✅ Управление расписанием питания
|
||||||
|
- ✅ Проверка статуса резервного копирования
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
### Метод 1: Локальный запуск
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод 2: Docker
|
||||||
|
|
||||||
|
1. Убедитесь, что Docker и Docker Compose установлены в вашей системе.
|
||||||
|
|
||||||
|
2. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Создайте файл `.env` на основе `.env-example` и заполните необходимыми значениями.
|
||||||
|
|
||||||
|
4. Запустите скрипт развертывания:
|
||||||
|
```bash
|
||||||
|
# Linux/macOS
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
deploy.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Или запустите вручную:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Проверьте статус:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Просмотр логов:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Дополнительная информация о Docker-развертывании доступна в файле [README_DOCKER.md](README_DOCKER.md).
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Информационные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
- `/temperature` - Температура устройства
|
||||||
|
- `/processes` - Список активных процессов
|
||||||
|
- `/network` - Сетевая информация
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/schedule` - Расписание питания
|
||||||
|
- `/browse` - Просмотр файлов
|
||||||
|
- `/search <запрос>` - Поиск файлов
|
||||||
|
- `/updates` - Проверка обновлений
|
||||||
|
- `/backup` - Статус резервного копирования
|
||||||
|
- `/quota` - Квоты пользователей
|
||||||
|
|
||||||
|
### Быстрые команды
|
||||||
|
- `/quickreboot` - Быстрая перезагрузка
|
||||||
|
- `/wakeup` - Пробуждение NAS (WOL)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .env-example # Пример файла переменных окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
├── .dockerignore # Файлы, игнорируемые при сборке Docker-образа
|
||||||
|
├── Dockerfile # Инструкции для сборки Docker-образа
|
||||||
|
├── docker-compose.yml # Конфигурация Docker Compose
|
||||||
|
├── deploy.sh # Скрипт развёртывания для Linux
|
||||||
|
├── deploy.cmd # Скрипт развёртывания для Windows
|
||||||
|
├── entrypoint.sh # Скрипт для запуска в Docker
|
||||||
|
├── README.md # Основная документация
|
||||||
|
├── README_DOCKER.md # Документация по Docker-развёртыванию
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Устранение неисправностей
|
||||||
|
|
||||||
|
### Проблемы с подключением к Synology NAS
|
||||||
|
|
||||||
|
1. Убедитесь, что NAS доступен по сети (можно проверить с помощью команды `ping`).
|
||||||
|
2. Проверьте правильность логина и пароля в `.env`.
|
||||||
|
3. Убедитесь, что DSM API включено в настройках NAS.
|
||||||
|
|
||||||
|
### Проблемы с Docker
|
||||||
|
|
||||||
|
1. Проверьте статус контейнера: `docker-compose ps`
|
||||||
|
2. Просмотрите логи: `docker-compose logs -f`
|
||||||
|
3. Перезапустите контейнер: `docker-compose restart`
|
||||||
|
4. Проверьте состояние здоровья: `docker inspect --format="{{json .State.Health}}" synology-power-control-bot`
|
||||||
|
5. Проверьте, что все переменные окружения корректно переданы в контейнер.
|
||||||
|
|
||||||
|
### Обновление в Docker
|
||||||
|
|
||||||
|
Для обновления бота в Docker:
|
||||||
|
|
||||||
|
1. Остановите контейнеры:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Загрузите обновления (если используете Git):
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Запустите контейнеры заново:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
236
.history/README_20250830103154.md
Normal file
236
.history/README_20250830103154.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# Synology Power Control Bot
|
||||||
|
|
||||||
|
Telegram-бот для удаленного управления питанием сетевого хранилища Synology NAS (DS223j и другие модели).
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
### Управление питанием
|
||||||
|
- ✅ Включение питания через Wake-on-LAN
|
||||||
|
- ✅ Выключение питания через API DSM
|
||||||
|
- ✅ Перезагрузка системы с отслеживанием статуса
|
||||||
|
|
||||||
|
### Мониторинг системы
|
||||||
|
- ✅ Проверка онлайн статуса NAS
|
||||||
|
- ✅ Информация о системе (модель, версия DSM, время работы)
|
||||||
|
- ✅ Мониторинг загрузки CPU и памяти
|
||||||
|
- ✅ Данные о температуре и сетевой активности
|
||||||
|
- ✅ Статус хранилища и дисков
|
||||||
|
- ✅ Информация о безопасности системы
|
||||||
|
- ✅ Список активных процессов
|
||||||
|
- ✅ Мониторинг сетевых подключений
|
||||||
|
|
||||||
|
### Управление данными
|
||||||
|
- ✅ Просмотр списка общих папок
|
||||||
|
- ✅ Информация о томах и дисках
|
||||||
|
- ✅ Статистика использования дисков
|
||||||
|
- ✅ Просмотр файлов и папок
|
||||||
|
- ✅ Поиск файлов
|
||||||
|
- ✅ Мониторинг квот пользователей
|
||||||
|
|
||||||
|
### Безопасность
|
||||||
|
- ✅ Ограничение доступа по ID пользователей Telegram
|
||||||
|
- ✅ Безопасное хранение учетных данных
|
||||||
|
|
||||||
|
### Дополнительные функции
|
||||||
|
- ✅ Мониторинг обновлений DSM и пакетов
|
||||||
|
- ✅ Управление расписанием питания
|
||||||
|
- ✅ Проверка статуса резервного копирования
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Python 3.8+
|
||||||
|
- Synology NAS с включенным WoL
|
||||||
|
- Учетная запись администратора DSM
|
||||||
|
- Telegram Bot API Token
|
||||||
|
- Доступ к порту API Synology DSM (обычно 5000 или 5001)
|
||||||
|
|
||||||
|
## Установка и настройка
|
||||||
|
|
||||||
|
### Метод 1: Локальный запуск
|
||||||
|
|
||||||
|
1. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Установите зависимости:
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Настройте параметры в файле `.env`:
|
||||||
|
```
|
||||||
|
# Telegram Bot Configuration
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321
|
||||||
|
|
||||||
|
# Synology NAS Configuration
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000
|
||||||
|
SYNOLOGY_USERNAME=admin
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=False
|
||||||
|
SYNOLOGY_TIMEOUT=10
|
||||||
|
|
||||||
|
# Wake-on-LAN Configuration
|
||||||
|
SYNOLOGY_MAC=00:11:22:33:44:55
|
||||||
|
WOL_PORT=9
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите бота:
|
||||||
|
```bash
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Метод 2: Docker
|
||||||
|
|
||||||
|
1. Убедитесь, что Docker и Docker Compose установлены в вашей системе.
|
||||||
|
|
||||||
|
2. Клонируйте репозиторий:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/synology_power_control_bot.git
|
||||||
|
cd synology_power_control_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Создайте файл `.env` на основе `.env-example` и заполните необходимыми значениями.
|
||||||
|
|
||||||
|
4. Запустите скрипт развертывания:
|
||||||
|
```bash
|
||||||
|
# Linux/macOS
|
||||||
|
chmod +x deploy.sh
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
deploy.cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Или запустите вручную:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Проверьте статус:
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Просмотр логов:
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
Дополнительная информация о Docker-развертывании доступна в файле [README_DOCKER.md](README_DOCKER.md).
|
||||||
|
|
||||||
|
## Подготовка Synology NAS
|
||||||
|
|
||||||
|
1. Включите Wake-on-LAN в настройках DSM:
|
||||||
|
- Панель управления > Сеть > Общие > Wake-on-LAN
|
||||||
|
|
||||||
|
2. Убедитесь, что API DSM включено:
|
||||||
|
- Панель управления > Службы терминала и SNMP > Включить DSM API
|
||||||
|
|
||||||
|
3. Узнайте MAC-адрес вашего NAS:
|
||||||
|
- Панель управления > Сеть > Сетевой интерфейс
|
||||||
|
|
||||||
|
## Команды бота
|
||||||
|
|
||||||
|
### Основные команды
|
||||||
|
- `/start` - Начало работы с ботом
|
||||||
|
- `/status` - Проверка текущего статуса NAS
|
||||||
|
- `/power` - Управление питанием NAS (включение, выключение, перезагрузка)
|
||||||
|
- `/help` - Вывод справочной информации
|
||||||
|
|
||||||
|
### Информационные команды
|
||||||
|
- `/system` - Подробная информация о системе
|
||||||
|
- `/storage` - Информация о хранилище и дисках
|
||||||
|
- `/shares` - Список общих папок
|
||||||
|
- `/load` - Текущая нагрузка на систему
|
||||||
|
- `/security` - Статус безопасности системы
|
||||||
|
- `/temperature` - Температура устройства
|
||||||
|
- `/processes` - Список активных процессов
|
||||||
|
- `/network` - Сетевая информация
|
||||||
|
|
||||||
|
### Расширенные команды
|
||||||
|
- `/schedule` - Расписание питания
|
||||||
|
- `/browse` - Просмотр файлов
|
||||||
|
- `/search <запрос>` - Поиск файлов
|
||||||
|
- `/updates` - Проверка обновлений
|
||||||
|
- `/backup` - Статус резервного копирования
|
||||||
|
- `/quota` - Квоты пользователей
|
||||||
|
|
||||||
|
### Быстрые команды
|
||||||
|
- `/quickreboot` - Быстрая перезагрузка
|
||||||
|
- `/wakeup` - Пробуждение NAS (WOL)
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── logs/ # Директория для логов
|
||||||
|
├── src/ # Исходный код
|
||||||
|
│ ├── api/ # Модули для работы с API
|
||||||
|
│ │ └── synology.py # API для работы с Synology NAS
|
||||||
|
│ ├── config/ # Модули конфигурации
|
||||||
|
│ │ └── config.py # Основная конфигурация
|
||||||
|
│ ├── handlers/ # Обработчики команд бота
|
||||||
|
│ │ └── command_handlers.py
|
||||||
|
│ ├── utils/ # Вспомогательные утилиты
|
||||||
|
│ │ └── logger.py # Настройка логирования
|
||||||
|
│ └── bot.py # Основной файл запуска бота
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── .env-example # Пример файла переменных окружения
|
||||||
|
├── .gitignore # Файл игнорирования Git
|
||||||
|
├── .dockerignore # Файлы, игнорируемые при сборке Docker-образа
|
||||||
|
├── Dockerfile # Инструкции для сборки Docker-образа
|
||||||
|
├── docker-compose.yml # Конфигурация Docker Compose
|
||||||
|
├── deploy.sh # Скрипт развёртывания для Linux
|
||||||
|
├── deploy.cmd # Скрипт развёртывания для Windows
|
||||||
|
├── entrypoint.sh # Скрипт для запуска в Docker
|
||||||
|
├── README.md # Основная документация
|
||||||
|
├── README_DOCKER.md # Документация по Docker-развёртыванию
|
||||||
|
└── requirements.txt # Зависимости проекта
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
Бот имеет систему авторизации на основе ID пользователей Telegram. Убедитесь, что указали правильные ID в переменной `ADMIN_USER_IDS` в файле `.env`.
|
||||||
|
|
||||||
|
## Устранение неисправностей
|
||||||
|
|
||||||
|
### Проблемы с подключением к Synology NAS
|
||||||
|
|
||||||
|
1. Убедитесь, что NAS доступен по сети (можно проверить с помощью команды `ping`).
|
||||||
|
2. Проверьте правильность логина и пароля в `.env`.
|
||||||
|
3. Убедитесь, что DSM API включено в настройках NAS.
|
||||||
|
|
||||||
|
### Проблемы с Docker
|
||||||
|
|
||||||
|
1. Проверьте статус контейнера: `docker-compose ps`
|
||||||
|
2. Просмотрите логи: `docker-compose logs -f`
|
||||||
|
3. Перезапустите контейнер: `docker-compose restart`
|
||||||
|
4. Проверьте состояние здоровья: `docker inspect --format="{{json .State.Health}}" synology-power-control-bot`
|
||||||
|
5. Проверьте, что все переменные окружения корректно переданы в контейнер.
|
||||||
|
|
||||||
|
### Обновление в Docker
|
||||||
|
|
||||||
|
Для обновления бота в Docker:
|
||||||
|
|
||||||
|
1. Остановите контейнеры:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Загрузите обновления (если используете Git):
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Запустите контейнеры заново:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
106
.history/README_DOCKER_20250830102736.md
Normal file
106
.history/README_DOCKER_20250830102736.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Synology Power Control Bot - Docker Deployment
|
||||||
|
|
||||||
|
## Подготовка к развертыванию
|
||||||
|
|
||||||
|
Перед развертыванием в Docker убедитесь, что:
|
||||||
|
|
||||||
|
1. Docker и Docker Compose установлены в вашей системе.
|
||||||
|
2. Файл `.env` настроен с правильными значениями.
|
||||||
|
|
||||||
|
## Структура проекта для Docker
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── src/ # Исходный код бота
|
||||||
|
├── logs/ # Папка для логов (будет смонтирована как том)
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── requirements.txt # Зависимости Python
|
||||||
|
├── Dockerfile # Инструкции для сборки образа
|
||||||
|
├── docker-compose.yml # Конфигурация Docker Compose
|
||||||
|
└── run.py # Точка входа
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка переменных окружения
|
||||||
|
|
||||||
|
Убедитесь, что файл `.env` содержит все необходимые переменные:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Telegram Bot API
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321 # ID пользователей-администраторов через запятую
|
||||||
|
|
||||||
|
# Synology NAS
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000 # Обычно 5000 для HTTP и 5001 для HTTPS
|
||||||
|
SYNOLOGY_USERNAME=your_username
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=True # Использовать HTTPS
|
||||||
|
SYNOLOGY_VERIFY_SSL=False # Проверка SSL-сертификата
|
||||||
|
SYNOLOGY_TIMEOUT=10 # Таймаут для API запросов в секундах
|
||||||
|
SYNOLOGY_API_VERSION=1 # Версия API
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.System # API для управления питанием
|
||||||
|
|
||||||
|
# WOL (Wake-on-LAN)
|
||||||
|
MAC_ADDRESS=00:11:22:33:44:55 # MAC-адрес Synology NAS
|
||||||
|
WOL_BROADCAST=255.255.255.255 # Broadcast-адрес для WOL
|
||||||
|
WOL_PORT=9 # Порт для WOL (обычно 7 или 9)
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сборка и запуск
|
||||||
|
|
||||||
|
### Сборка и запуск контейнеров
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Просмотр логов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Остановка контейнеров
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновление
|
||||||
|
|
||||||
|
Для обновления бота:
|
||||||
|
|
||||||
|
1. Остановите контейнеры:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Скачайте последние изменения (если используете Git):
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Соберите и запустите контейнеры заново:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Устранение неполадок
|
||||||
|
|
||||||
|
### Проверка статуса контейнера
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка логов контейнера
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f synology-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Подключение к контейнеру
|
||||||
|
```bash
|
||||||
|
docker-compose exec synology-bot bash
|
||||||
|
```
|
||||||
106
.history/README_DOCKER_20250830103154.md
Normal file
106
.history/README_DOCKER_20250830103154.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Synology Power Control Bot - Docker Deployment
|
||||||
|
|
||||||
|
## Подготовка к развертыванию
|
||||||
|
|
||||||
|
Перед развертыванием в Docker убедитесь, что:
|
||||||
|
|
||||||
|
1. Docker и Docker Compose установлены в вашей системе.
|
||||||
|
2. Файл `.env` настроен с правильными значениями.
|
||||||
|
|
||||||
|
## Структура проекта для Docker
|
||||||
|
|
||||||
|
```
|
||||||
|
synology_power_control_bot/
|
||||||
|
├── src/ # Исходный код бота
|
||||||
|
├── logs/ # Папка для логов (будет смонтирована как том)
|
||||||
|
├── .env # Файл с переменными окружения
|
||||||
|
├── requirements.txt # Зависимости Python
|
||||||
|
├── Dockerfile # Инструкции для сборки образа
|
||||||
|
├── docker-compose.yml # Конфигурация Docker Compose
|
||||||
|
└── run.py # Точка входа
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка переменных окружения
|
||||||
|
|
||||||
|
Убедитесь, что файл `.env` содержит все необходимые переменные:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Telegram Bot API
|
||||||
|
TELEGRAM_TOKEN=your_telegram_bot_token
|
||||||
|
ADMIN_USER_IDS=123456789,987654321 # ID пользователей-администраторов через запятую
|
||||||
|
|
||||||
|
# Synology NAS
|
||||||
|
SYNOLOGY_HOST=192.168.1.100
|
||||||
|
SYNOLOGY_PORT=5000 # Обычно 5000 для HTTP и 5001 для HTTPS
|
||||||
|
SYNOLOGY_USERNAME=your_username
|
||||||
|
SYNOLOGY_PASSWORD=your_password
|
||||||
|
SYNOLOGY_SECURE=True # Использовать HTTPS
|
||||||
|
SYNOLOGY_VERIFY_SSL=False # Проверка SSL-сертификата
|
||||||
|
SYNOLOGY_TIMEOUT=10 # Таймаут для API запросов в секундах
|
||||||
|
SYNOLOGY_API_VERSION=1 # Версия API
|
||||||
|
SYNOLOGY_POWER_API=SYNO.Core.System # API для управления питанием
|
||||||
|
|
||||||
|
# WOL (Wake-on-LAN)
|
||||||
|
MAC_ADDRESS=00:11:22:33:44:55 # MAC-адрес Synology NAS
|
||||||
|
WOL_BROADCAST=255.255.255.255 # Broadcast-адрес для WOL
|
||||||
|
WOL_PORT=9 # Порт для WOL (обычно 7 или 9)
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
```
|
||||||
|
|
||||||
|
## Сборка и запуск
|
||||||
|
|
||||||
|
### Сборка и запуск контейнеров
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Просмотр логов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Остановка контейнеров
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновление
|
||||||
|
|
||||||
|
Для обновления бота:
|
||||||
|
|
||||||
|
1. Остановите контейнеры:
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Скачайте последние изменения (если используете Git):
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Соберите и запустите контейнеры заново:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Устранение неполадок
|
||||||
|
|
||||||
|
### Проверка статуса контейнера
|
||||||
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка логов контейнера
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f synology-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Подключение к контейнеру
|
||||||
|
```bash
|
||||||
|
docker-compose exec synology-bot bash
|
||||||
|
```
|
||||||
49
.history/deploy_20250830102934.sh
Normal file
49
.history/deploy_20250830102934.sh
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy.sh - Скрипт для развертывания Synology Power Control Bot
|
||||||
|
|
||||||
|
# Цвета для вывода
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Проверяем наличие Docker и Docker Compose
|
||||||
|
echo -e "${YELLOW}Проверка наличия Docker...${NC}"
|
||||||
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
echo -e "${RED}Ошибка: Docker не установлен.${NC}" >&2
|
||||||
|
echo -e "Установите Docker, следуя инструкциям: https://docs.docker.com/get-docker/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Проверка наличия Docker Compose...${NC}"
|
||||||
|
if ! [ -x "$(command -v docker-compose)" ] && ! [ -x "$(command -v docker compose)" ]; then
|
||||||
|
echo -e "${RED}Ошибка: Docker Compose не установлен.${NC}" >&2
|
||||||
|
echo -e "Установите Docker Compose, следуя инструкциям: https://docs.docker.com/compose/install/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем наличие файла .env
|
||||||
|
echo -e "${YELLOW}Проверка файла .env...${NC}"
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
echo -e "${RED}Ошибка: Файл .env не найден.${NC}" >&2
|
||||||
|
echo -e "Создайте файл .env с необходимыми переменными окружения."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Создаем директорию для логов
|
||||||
|
echo -e "${YELLOW}Создание директории для логов...${NC}"
|
||||||
|
mkdir -p logs
|
||||||
|
chmod 777 logs
|
||||||
|
|
||||||
|
# Сборка и запуск Docker контейнеров
|
||||||
|
echo -e "${YELLOW}Сборка и запуск Docker контейнеров...${NC}"
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Проверка статуса контейнеров
|
||||||
|
echo -e "${YELLOW}Проверка статуса контейнеров...${NC}"
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
echo -e "${GREEN}Развертывание завершено успешно!${NC}"
|
||||||
|
echo -e "Для просмотра логов: ${YELLOW}docker-compose logs -f${NC}"
|
||||||
|
echo -e "Для остановки: ${YELLOW}docker-compose down${NC}"
|
||||||
45
.history/deploy_20250830102949.cmd
Normal file
45
.history/deploy_20250830102949.cmd
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@echo off
|
||||||
|
REM deploy.cmd - Скрипт для развертывания Synology Power Control Bot на Windows
|
||||||
|
|
||||||
|
echo Проверка наличия Docker...
|
||||||
|
where docker >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
echo Ошибка: Docker не установлен.
|
||||||
|
echo Установите Docker Desktop, следуя инструкциям: https://docs.docker.com/desktop/windows/install/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Проверка наличия Docker Compose...
|
||||||
|
where docker-compose >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
docker compose version >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
echo Ошибка: Docker Compose не установлен.
|
||||||
|
echo Установите Docker Desktop, следуя инструкциям: https://docs.docker.com/desktop/windows/install/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Проверка файла .env...
|
||||||
|
if not exist .env (
|
||||||
|
echo Ошибка: Файл .env не найден.
|
||||||
|
echo Создайте файл .env с необходимыми переменными окружения.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Создание директории для логов...
|
||||||
|
if not exist logs mkdir logs
|
||||||
|
|
||||||
|
echo Сборка и запуск Docker контейнеров...
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
echo Проверка статуса контейнеров...
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Развертывание завершено успешно!
|
||||||
|
echo Для просмотра логов: docker-compose logs -f
|
||||||
|
echo Для остановки: docker-compose down
|
||||||
|
|
||||||
|
pause
|
||||||
45
.history/deploy_20250830103154.cmd
Normal file
45
.history/deploy_20250830103154.cmd
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@echo off
|
||||||
|
REM deploy.cmd - Скрипт для развертывания Synology Power Control Bot на Windows
|
||||||
|
|
||||||
|
echo Проверка наличия Docker...
|
||||||
|
where docker >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
echo Ошибка: Docker не установлен.
|
||||||
|
echo Установите Docker Desktop, следуя инструкциям: https://docs.docker.com/desktop/windows/install/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Проверка наличия Docker Compose...
|
||||||
|
where docker-compose >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
docker compose version >nul 2>&1
|
||||||
|
if %ERRORLEVEL% NEQ 0 (
|
||||||
|
echo Ошибка: Docker Compose не установлен.
|
||||||
|
echo Установите Docker Desktop, следуя инструкциям: https://docs.docker.com/desktop/windows/install/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Проверка файла .env...
|
||||||
|
if not exist .env (
|
||||||
|
echo Ошибка: Файл .env не найден.
|
||||||
|
echo Создайте файл .env с необходимыми переменными окружения.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Создание директории для логов...
|
||||||
|
if not exist logs mkdir logs
|
||||||
|
|
||||||
|
echo Сборка и запуск Docker контейнеров...
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
echo Проверка статуса контейнеров...
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Развертывание завершено успешно!
|
||||||
|
echo Для просмотра логов: docker-compose logs -f
|
||||||
|
echo Для остановки: docker-compose down
|
||||||
|
|
||||||
|
pause
|
||||||
49
.history/deploy_20250830103154.sh
Normal file
49
.history/deploy_20250830103154.sh
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# deploy.sh - Скрипт для развертывания Synology Power Control Bot
|
||||||
|
|
||||||
|
# Цвета для вывода
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Проверяем наличие Docker и Docker Compose
|
||||||
|
echo -e "${YELLOW}Проверка наличия Docker...${NC}"
|
||||||
|
if ! [ -x "$(command -v docker)" ]; then
|
||||||
|
echo -e "${RED}Ошибка: Docker не установлен.${NC}" >&2
|
||||||
|
echo -e "Установите Docker, следуя инструкциям: https://docs.docker.com/get-docker/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Проверка наличия Docker Compose...${NC}"
|
||||||
|
if ! [ -x "$(command -v docker-compose)" ] && ! [ -x "$(command -v docker compose)" ]; then
|
||||||
|
echo -e "${RED}Ошибка: Docker Compose не установлен.${NC}" >&2
|
||||||
|
echo -e "Установите Docker Compose, следуя инструкциям: https://docs.docker.com/compose/install/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем наличие файла .env
|
||||||
|
echo -e "${YELLOW}Проверка файла .env...${NC}"
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
echo -e "${RED}Ошибка: Файл .env не найден.${NC}" >&2
|
||||||
|
echo -e "Создайте файл .env с необходимыми переменными окружения."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Создаем директорию для логов
|
||||||
|
echo -e "${YELLOW}Создание директории для логов...${NC}"
|
||||||
|
mkdir -p logs
|
||||||
|
chmod 777 logs
|
||||||
|
|
||||||
|
# Сборка и запуск Docker контейнеров
|
||||||
|
echo -e "${YELLOW}Сборка и запуск Docker контейнеров...${NC}"
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# Проверка статуса контейнеров
|
||||||
|
echo -e "${YELLOW}Проверка статуса контейнеров...${NC}"
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
echo -e "${GREEN}Развертывание завершено успешно!${NC}"
|
||||||
|
echo -e "Для просмотра логов: ${YELLOW}docker-compose logs -f${NC}"
|
||||||
|
echo -e "Для остановки: ${YELLOW}docker-compose down${NC}"
|
||||||
91
.history/diagnose_api_20250830081925.py
Normal file
91
.history/diagnose_api_20250830081925.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Диагностический скрипт для определения совместимых API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Добавляем родительскую директорию в sys.path
|
||||||
|
parent_dir = str(Path(__file__).resolve().parent.parent)
|
||||||
|
if parent_dir not in sys.path:
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
|
||||||
|
from src.api.api_discovery import discover_available_apis
|
||||||
|
from src.config.config import SYNOLOGY_HOST, SYNOLOGY_PORT, SYNOLOGY_SECURE
|
||||||
|
|
||||||
|
# Настройка логгера
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Точка входа для диагностического скрипта"""
|
||||||
|
parser = argparse.ArgumentParser(description='Synology API Diagnostic Tool')
|
||||||
|
parser.add_argument('--host', help='Synology host address', default=SYNOLOGY_HOST)
|
||||||
|
parser.add_argument('--port', type=int, help='Synology host port', default=SYNOLOGY_PORT)
|
||||||
|
parser.add_argument('--secure', action='store_true', help='Use HTTPS', default=SYNOLOGY_SECURE)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
protocol = "https" if args.secure else "http"
|
||||||
|
base_url = f"{protocol}://{args.host}:{args.port}/webapi"
|
||||||
|
|
||||||
|
print(f"Scanning APIs at {base_url}...")
|
||||||
|
|
||||||
|
apis = discover_available_apis(base_url)
|
||||||
|
|
||||||
|
if not apis:
|
||||||
|
print("No APIs were discovered. Check connection parameters.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Discovered {len(apis)} APIs")
|
||||||
|
|
||||||
|
# Анализ результатов
|
||||||
|
|
||||||
|
# 1. Ищем API для управления питанием
|
||||||
|
print("\nPower Management APIs:")
|
||||||
|
power_apis = [name for name in apis.keys() if "power" in name.lower()]
|
||||||
|
for api in power_apis:
|
||||||
|
info = apis[api]
|
||||||
|
print(f" - {api} (v{info.get('minVersion', 1)}-{info.get('maxVersion', 1)}), path: {info.get('path', 'entry.cgi')}")
|
||||||
|
|
||||||
|
# 2. Ищем API для информации о системе
|
||||||
|
print("\nSystem Information APIs:")
|
||||||
|
system_info_apis = [name for name in apis.keys() if ("system" in name.lower() or "dsm" in name.lower()) and "info" in name.lower()]
|
||||||
|
for api in system_info_apis:
|
||||||
|
info = apis[api]
|
||||||
|
print(f" - {api} (v{info.get('minVersion', 1)}-{info.get('maxVersion', 1)}), path: {info.get('path', 'entry.cgi')}")
|
||||||
|
|
||||||
|
# 3. Ищем API для перезагрузки
|
||||||
|
print("\nReboot/Restart APIs:")
|
||||||
|
reboot_apis = [name for name in apis.keys() if any(word in name.lower() for word in ["restart", "reboot", "power"])]
|
||||||
|
for api in reboot_apis:
|
||||||
|
info = apis[api]
|
||||||
|
print(f" - {api} (v{info.get('minVersion', 1)}-{info.get('maxVersion', 1)}), path: {info.get('path', 'entry.cgi')}")
|
||||||
|
|
||||||
|
print("\nRecommended API Settings:")
|
||||||
|
|
||||||
|
if power_apis:
|
||||||
|
recommended_power_api = max(power_apis, key=lambda x: apis[x].get('maxVersion', 1))
|
||||||
|
print(f"Power API: {recommended_power_api}, version: {apis[recommended_power_api].get('maxVersion', 1)}")
|
||||||
|
else:
|
||||||
|
print("Power API: Not found, falling back to SYNO.Core.System")
|
||||||
|
|
||||||
|
if system_info_apis:
|
||||||
|
recommended_info_api = max(system_info_apis, key=lambda x: apis[x].get('maxVersion', 1))
|
||||||
|
print(f"System Info API: {recommended_info_api}, version: {apis[recommended_info_api].get('maxVersion', 1)}")
|
||||||
|
else:
|
||||||
|
print("System Info API: Not found, falling back to SYNO.DSM.Info")
|
||||||
|
|
||||||
|
print("\nThese settings should be added to your .env file.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
91
.history/diagnose_api_20250830081957.py
Normal file
91
.history/diagnose_api_20250830081957.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Диагностический скрипт для определения совместимых API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Добавляем родительскую директорию в sys.path
|
||||||
|
parent_dir = str(Path(__file__).resolve().parent.parent)
|
||||||
|
if parent_dir not in sys.path:
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
|
||||||
|
from src.api.api_discovery import discover_available_apis
|
||||||
|
from src.config.config import SYNOLOGY_HOST, SYNOLOGY_PORT, SYNOLOGY_SECURE
|
||||||
|
|
||||||
|
# Настройка логгера
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Точка входа для диагностического скрипта"""
|
||||||
|
parser = argparse.ArgumentParser(description='Synology API Diagnostic Tool')
|
||||||
|
parser.add_argument('--host', help='Synology host address', default=SYNOLOGY_HOST)
|
||||||
|
parser.add_argument('--port', type=int, help='Synology host port', default=SYNOLOGY_PORT)
|
||||||
|
parser.add_argument('--secure', action='store_true', help='Use HTTPS', default=SYNOLOGY_SECURE)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
protocol = "https" if args.secure else "http"
|
||||||
|
base_url = f"{protocol}://{args.host}:{args.port}/webapi"
|
||||||
|
|
||||||
|
print(f"Scanning APIs at {base_url}...")
|
||||||
|
|
||||||
|
apis = discover_available_apis(base_url)
|
||||||
|
|
||||||
|
if not apis:
|
||||||
|
print("No APIs were discovered. Check connection parameters.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Discovered {len(apis)} APIs")
|
||||||
|
|
||||||
|
# Анализ результатов
|
||||||
|
|
||||||
|
# 1. Ищем API для управления питанием
|
||||||
|
print("\nPower Management APIs:")
|
||||||
|
power_apis = [name for name in apis.keys() if "power" in name.lower()]
|
||||||
|
for api in power_apis:
|
||||||
|
info = apis[api]
|
||||||
|
print(f" - {api} (v{info.get('minVersion', 1)}-{info.get('maxVersion', 1)}), path: {info.get('path', 'entry.cgi')}")
|
||||||
|
|
||||||
|
# 2. Ищем API для информации о системе
|
||||||
|
print("\nSystem Information APIs:")
|
||||||
|
system_info_apis = [name for name in apis.keys() if ("system" in name.lower() or "dsm" in name.lower()) and "info" in name.lower()]
|
||||||
|
for api in system_info_apis:
|
||||||
|
info = apis[api]
|
||||||
|
print(f" - {api} (v{info.get('minVersion', 1)}-{info.get('maxVersion', 1)}), path: {info.get('path', 'entry.cgi')}")
|
||||||
|
|
||||||
|
# 3. Ищем API для перезагрузки
|
||||||
|
print("\nReboot/Restart APIs:")
|
||||||
|
reboot_apis = [name for name in apis.keys() if any(word in name.lower() for word in ["restart", "reboot", "power"])]
|
||||||
|
for api in reboot_apis:
|
||||||
|
info = apis[api]
|
||||||
|
print(f" - {api} (v{info.get('minVersion', 1)}-{info.get('maxVersion', 1)}), path: {info.get('path', 'entry.cgi')}")
|
||||||
|
|
||||||
|
print("\nRecommended API Settings:")
|
||||||
|
|
||||||
|
if power_apis:
|
||||||
|
recommended_power_api = max(power_apis, key=lambda x: apis[x].get('maxVersion', 1))
|
||||||
|
print(f"Power API: {recommended_power_api}, version: {apis[recommended_power_api].get('maxVersion', 1)}")
|
||||||
|
else:
|
||||||
|
print("Power API: Not found, falling back to SYNO.Core.System")
|
||||||
|
|
||||||
|
if system_info_apis:
|
||||||
|
recommended_info_api = max(system_info_apis, key=lambda x: apis[x].get('maxVersion', 1))
|
||||||
|
print(f"System Info API: {recommended_info_api}, version: {apis[recommended_info_api].get('maxVersion', 1)}")
|
||||||
|
else:
|
||||||
|
print("System Info API: Not found, falling back to SYNO.DSM.Info")
|
||||||
|
|
||||||
|
print("\nThese settings should be added to your .env file.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
293
.history/direct_api_test_20250830084231.py
Normal file
293
.history/direct_api_test_20250830084231.py
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Тестовый скрипт для прямого доступа к API Synology для получения информации о системе.
|
||||||
|
Используется для отладки и определения совместимых API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import urllib3
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from urllib3.util import Retry
|
||||||
|
|
||||||
|
# Добавляем корневой каталог в путь для импорта
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from src.config.config import (
|
||||||
|
SYNOLOGY_HOST,
|
||||||
|
SYNOLOGY_PORT,
|
||||||
|
SYNOLOGY_USERNAME,
|
||||||
|
SYNOLOGY_PASSWORD,
|
||||||
|
SYNOLOGY_SECURE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Отключение предупреждений о небезопасных SSL-соединениях
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def direct_api_test():
|
||||||
|
"""Прямой тест API без использования классов для определения проблемы"""
|
||||||
|
# Создаем базовую сессию
|
||||||
|
session = requests.Session()
|
||||||
|
session.verify = False # Отключаем проверку SSL
|
||||||
|
|
||||||
|
# Добавляем повторные попытки для HTTP-запросов
|
||||||
|
retry_strategy = Retry(
|
||||||
|
total=3,
|
||||||
|
status_forcelist=[429, 500, 502, 503, 504],
|
||||||
|
allowed_methods=["GET", "POST"],
|
||||||
|
backoff_factor=1.0
|
||||||
|
)
|
||||||
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||||||
|
session.mount("http://", adapter)
|
||||||
|
session.mount("https://", adapter)
|
||||||
|
|
||||||
|
# Формируем базовый URL
|
||||||
|
protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
base_url = f"{protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
logger.info(f"Тестирование прямого API доступа к {base_url}")
|
||||||
|
|
||||||
|
# Шаг 1: Авторизация
|
||||||
|
logger.info("Шаг 1: Попытка авторизации...")
|
||||||
|
|
||||||
|
# Сначала получаем информацию об API авторизации
|
||||||
|
api_info_url = f"{base_url}/entry.cgi"
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": "SYNO.API.Auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
auth_info_response = session.get(api_info_url, params=api_info_params, timeout=10)
|
||||||
|
auth_info_data = auth_info_response.json()
|
||||||
|
|
||||||
|
if auth_info_data.get("success"):
|
||||||
|
auth_info = auth_info_data.get("data", {}).get("SYNO.API.Auth", {})
|
||||||
|
auth_path = auth_info.get("path", "auth.cgi")
|
||||||
|
auth_max_version = auth_info.get("maxVersion", 6)
|
||||||
|
|
||||||
|
logger.info(f"API авторизации: путь={auth_path}, макс. версия={auth_max_version}")
|
||||||
|
|
||||||
|
# Пробуем версию 6 или максимальную доступную
|
||||||
|
auth_version = min(6, auth_max_version)
|
||||||
|
|
||||||
|
# Выполняем авторизацию
|
||||||
|
auth_url = f"{base_url}/{auth_path}"
|
||||||
|
auth_params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": str(auth_version),
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "DirectApiTest",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Для версии 6+ используем немного другой формат
|
||||||
|
if auth_version >= 6:
|
||||||
|
auth_params["enable_syno_token"] = "yes"
|
||||||
|
|
||||||
|
logger.info(f"Авторизация с использованием SYNO.API.Auth v{auth_version}")
|
||||||
|
auth_response = session.get(auth_url, params=auth_params, timeout=10)
|
||||||
|
auth_data = auth_response.json()
|
||||||
|
|
||||||
|
if auth_data.get("success"):
|
||||||
|
sid = auth_data.get("data", {}).get("sid")
|
||||||
|
logger.info(f"Авторизация успешна! SID: {sid[:10]}...")
|
||||||
|
|
||||||
|
# Шаг 2: Тестирование различных API для получения информации о системе
|
||||||
|
logger.info("Шаг 2: Тестирование различных API для получения информации о системе")
|
||||||
|
|
||||||
|
# Создаем список API для тестирования
|
||||||
|
api_to_test = [
|
||||||
|
{"name": "SYNO.DSM.Info", "method": "getinfo", "version": 1},
|
||||||
|
{"name": "SYNO.DSM.Info", "method": "getinfo", "version": 2},
|
||||||
|
{"name": "SYNO.Core.System", "method": "info", "version": 1},
|
||||||
|
{"name": "SYNO.Core.System", "method": "info", "version": 2},
|
||||||
|
{"name": "SYNO.Core.System.Status", "method": "get", "version": 1},
|
||||||
|
{"name": "SYNO.Core.System.Status", "method": "get", "version": 2},
|
||||||
|
{"name": "SYNO.Core.System.Utilization", "method": "get", "version": 1},
|
||||||
|
{"name": "SYNO.Core.CurrentConnection", "method": "list", "version": 1}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Перебираем все API и тестируем их
|
||||||
|
for api in api_to_test:
|
||||||
|
# Сначала получаем информацию о конкретном API
|
||||||
|
try:
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": api["name"]
|
||||||
|
}
|
||||||
|
|
||||||
|
api_info_resp = session.get(api_info_url, params=api_info_params, timeout=10)
|
||||||
|
api_info_data = api_info_resp.json()
|
||||||
|
|
||||||
|
if api_info_data.get("success") and api["name"] in api_info_data.get("data", {}):
|
||||||
|
api_details = api_info_data["data"][api["name"]]
|
||||||
|
api_path = api_details.get("path", "entry.cgi")
|
||||||
|
api_min_version = api_details.get("minVersion", 1)
|
||||||
|
api_max_version = api_details.get("maxVersion", 1)
|
||||||
|
|
||||||
|
# Проверяем, поддерживается ли указанная версия
|
||||||
|
if api["version"] < api_min_version:
|
||||||
|
logger.warning(f"{api['name']} v{api['version']} ниже минимальной {api_min_version}, используем {api_min_version}")
|
||||||
|
test_version = api_min_version
|
||||||
|
elif api["version"] > api_max_version:
|
||||||
|
logger.warning(f"{api['name']} v{api['version']} выше максимальной {api_max_version}, используем {api_max_version}")
|
||||||
|
test_version = api_max_version
|
||||||
|
else:
|
||||||
|
test_version = api["version"]
|
||||||
|
|
||||||
|
# Выполняем запрос API
|
||||||
|
test_url = f"{base_url}/{api_path}"
|
||||||
|
test_params = {
|
||||||
|
"api": api["name"],
|
||||||
|
"version": str(test_version),
|
||||||
|
"method": api["method"],
|
||||||
|
"_sid": sid # Используем sid для аутентификации
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"Тестирование {api['name']}.{api['method']} v{test_version}")
|
||||||
|
test_response = session.get(test_url, params=test_params, timeout=10)
|
||||||
|
test_data = test_response.json()
|
||||||
|
|
||||||
|
if test_data.get("success"):
|
||||||
|
logger.info(f"API {api['name']}.{api['method']} v{test_version} РАБОТАЕТ!")
|
||||||
|
logger.info(f"Результат: {json.dumps(test_data.get('data', {}), indent=2)[:200]}...")
|
||||||
|
else:
|
||||||
|
error_code = test_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"API {api['name']}.{api['method']} v{test_version} ОШИБКА: {error_code}")
|
||||||
|
|
||||||
|
# Если ошибка связана с сессией, попробуем еще раз авторизоваться
|
||||||
|
if error_code == 119: # Session timeout
|
||||||
|
logger.info("Повторная авторизация из-за ошибки 119...")
|
||||||
|
|
||||||
|
# Создаем новую сессию
|
||||||
|
new_session = requests.Session()
|
||||||
|
new_session.verify = False
|
||||||
|
|
||||||
|
auth_response = new_session.get(auth_url, params=auth_params, timeout=10)
|
||||||
|
auth_data = auth_response.json()
|
||||||
|
|
||||||
|
if auth_data.get("success"):
|
||||||
|
new_sid = auth_data.get("data", {}).get("sid")
|
||||||
|
logger.info(f"Повторная авторизация успешна! Новый SID: {new_sid[:10]}...")
|
||||||
|
|
||||||
|
# Пробуем запрос с новым SID
|
||||||
|
test_params["_sid"] = new_sid
|
||||||
|
logger.info(f"Повторное тестирование {api['name']}.{api['method']} v{test_version}")
|
||||||
|
test_response = new_session.get(test_url, params=test_params, timeout=10)
|
||||||
|
test_data = test_response.json()
|
||||||
|
|
||||||
|
if test_data.get("success"):
|
||||||
|
logger.info(f"API {api['name']}.{api['method']} v{test_version} теперь РАБОТАЕТ!")
|
||||||
|
logger.info(f"Результат с новой сессией: {json.dumps(test_data.get('data', {}), indent=2)[:200]}...")
|
||||||
|
else:
|
||||||
|
error_code = test_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"API {api['name']}.{api['method']} v{test_version} ВСЕ ЕЩЕ С ОШИБКОЙ: {error_code}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"API {api['name']} не найден в информации API")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при тестировании {api['name']}.{api['method']} v{api['version']}: {str(e)}")
|
||||||
|
|
||||||
|
# Шаг 3: Тестирование комбинации запросов для решения проблемы
|
||||||
|
logger.info("Шаг 3: Тестирование комбинации запросов для решения проблемы")
|
||||||
|
|
||||||
|
# Создаем новую сессию для каждого запроса
|
||||||
|
for api in [{"name": "SYNO.DSM.Info", "method": "getinfo", "version": 1}]:
|
||||||
|
try:
|
||||||
|
fresh_session = requests.Session()
|
||||||
|
fresh_session.verify = False
|
||||||
|
|
||||||
|
# Авторизуемся
|
||||||
|
auth_response = fresh_session.get(auth_url, params=auth_params, timeout=10)
|
||||||
|
auth_data = auth_response.json()
|
||||||
|
|
||||||
|
if auth_data.get("success"):
|
||||||
|
fresh_sid = auth_data.get("data", {}).get("sid")
|
||||||
|
logger.info(f"Авторизация в новой сессии успешна! SID: {fresh_sid[:10]}...")
|
||||||
|
|
||||||
|
# Сразу же делаем запрос для получения информации в той же сессии
|
||||||
|
test_params = {
|
||||||
|
"api": api["name"],
|
||||||
|
"version": str(api["version"]),
|
||||||
|
"method": api["method"],
|
||||||
|
"_sid": fresh_sid
|
||||||
|
}
|
||||||
|
|
||||||
|
test_url = f"{base_url}/entry.cgi" # Используем entry.cgi по умолчанию
|
||||||
|
logger.info(f"Тест в свежей сессии: {api['name']}.{api['method']} v{api['version']}")
|
||||||
|
test_response = fresh_session.get(test_url, params=test_params, timeout=10)
|
||||||
|
test_data = test_response.json()
|
||||||
|
|
||||||
|
if test_data.get("success"):
|
||||||
|
logger.info(f"API в свежей сессии РАБОТАЕТ!")
|
||||||
|
logger.info(f"Результат: {json.dumps(test_data.get('data', {}), indent=2)[:200]}...")
|
||||||
|
else:
|
||||||
|
error_code = test_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"API в свежей сессии ОШИБКА: {error_code}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при тестировании свежей сессии: {str(e)}")
|
||||||
|
|
||||||
|
# Шаг 4: Получаем информацию об остальных API
|
||||||
|
logger.info("Шаг 4: Получаем информацию о доступных API для уточнения проблемы")
|
||||||
|
|
||||||
|
# Запрашиваем все API из SYNO.API.Info
|
||||||
|
try:
|
||||||
|
all_api_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
all_api_response = session.get(api_info_url, params=all_api_params, timeout=15) # Больший таймаут для большого ответа
|
||||||
|
all_api_data = all_api_response.json()
|
||||||
|
|
||||||
|
if all_api_data.get("success"):
|
||||||
|
api_list = all_api_data.get("data", {})
|
||||||
|
logger.info(f"Получен список всех API. Найдено {len(api_list)} API.")
|
||||||
|
|
||||||
|
# Ищем интересующие нас API для отладки
|
||||||
|
interested_in = ["SYNO.DSM.Info", "SYNO.Core.System", "SYNO.Core.Hardware",
|
||||||
|
"SYNO.Core.System.Status", "SYNO.API.Auth"]
|
||||||
|
|
||||||
|
logger.info("Информация о важных API:")
|
||||||
|
for api_name in interested_in:
|
||||||
|
if api_name in api_list:
|
||||||
|
logger.info(f"{api_name}: {api_list[api_name]}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"API {api_name} не найден")
|
||||||
|
else:
|
||||||
|
logger.error("Не удалось получить список всех API")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при получении списка API: {str(e)}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
error_code = auth_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Авторизация не удалась! Код ошибки: {error_code}")
|
||||||
|
else:
|
||||||
|
logger.error("Не удалось получить информацию об API авторизации")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Произошла ошибка при выполнении теста: {str(e)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logger.info("Запуск прямого теста API Synology")
|
||||||
|
direct_api_test()
|
||||||
293
.history/direct_api_test_20250830084257.py
Normal file
293
.history/direct_api_test_20250830084257.py
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Тестовый скрипт для прямого доступа к API Synology для получения информации о системе.
|
||||||
|
Используется для отладки и определения совместимых API.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import urllib3
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from urllib3.util import Retry
|
||||||
|
|
||||||
|
# Добавляем корневой каталог в путь для импорта
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from src.config.config import (
|
||||||
|
SYNOLOGY_HOST,
|
||||||
|
SYNOLOGY_PORT,
|
||||||
|
SYNOLOGY_USERNAME,
|
||||||
|
SYNOLOGY_PASSWORD,
|
||||||
|
SYNOLOGY_SECURE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Отключение предупреждений о небезопасных SSL-соединениях
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
# Настройка логирования
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def direct_api_test():
|
||||||
|
"""Прямой тест API без использования классов для определения проблемы"""
|
||||||
|
# Создаем базовую сессию
|
||||||
|
session = requests.Session()
|
||||||
|
session.verify = False # Отключаем проверку SSL
|
||||||
|
|
||||||
|
# Добавляем повторные попытки для HTTP-запросов
|
||||||
|
retry_strategy = Retry(
|
||||||
|
total=3,
|
||||||
|
status_forcelist=[429, 500, 502, 503, 504],
|
||||||
|
allowed_methods=["GET", "POST"],
|
||||||
|
backoff_factor=1.0
|
||||||
|
)
|
||||||
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
||||||
|
session.mount("http://", adapter)
|
||||||
|
session.mount("https://", adapter)
|
||||||
|
|
||||||
|
# Формируем базовый URL
|
||||||
|
protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
base_url = f"{protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
logger.info(f"Тестирование прямого API доступа к {base_url}")
|
||||||
|
|
||||||
|
# Шаг 1: Авторизация
|
||||||
|
logger.info("Шаг 1: Попытка авторизации...")
|
||||||
|
|
||||||
|
# Сначала получаем информацию об API авторизации
|
||||||
|
api_info_url = f"{base_url}/entry.cgi"
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": "SYNO.API.Auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
auth_info_response = session.get(api_info_url, params=api_info_params, timeout=10)
|
||||||
|
auth_info_data = auth_info_response.json()
|
||||||
|
|
||||||
|
if auth_info_data.get("success"):
|
||||||
|
auth_info = auth_info_data.get("data", {}).get("SYNO.API.Auth", {})
|
||||||
|
auth_path = auth_info.get("path", "auth.cgi")
|
||||||
|
auth_max_version = auth_info.get("maxVersion", 6)
|
||||||
|
|
||||||
|
logger.info(f"API авторизации: путь={auth_path}, макс. версия={auth_max_version}")
|
||||||
|
|
||||||
|
# Пробуем версию 6 или максимальную доступную
|
||||||
|
auth_version = min(6, auth_max_version)
|
||||||
|
|
||||||
|
# Выполняем авторизацию
|
||||||
|
auth_url = f"{base_url}/{auth_path}"
|
||||||
|
auth_params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": str(auth_version),
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "DirectApiTest",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Для версии 6+ используем немного другой формат
|
||||||
|
if auth_version >= 6:
|
||||||
|
auth_params["enable_syno_token"] = "yes"
|
||||||
|
|
||||||
|
logger.info(f"Авторизация с использованием SYNO.API.Auth v{auth_version}")
|
||||||
|
auth_response = session.get(auth_url, params=auth_params, timeout=10)
|
||||||
|
auth_data = auth_response.json()
|
||||||
|
|
||||||
|
if auth_data.get("success"):
|
||||||
|
sid = auth_data.get("data", {}).get("sid")
|
||||||
|
logger.info(f"Авторизация успешна! SID: {sid[:10]}...")
|
||||||
|
|
||||||
|
# Шаг 2: Тестирование различных API для получения информации о системе
|
||||||
|
logger.info("Шаг 2: Тестирование различных API для получения информации о системе")
|
||||||
|
|
||||||
|
# Создаем список API для тестирования
|
||||||
|
api_to_test = [
|
||||||
|
{"name": "SYNO.DSM.Info", "method": "getinfo", "version": 1},
|
||||||
|
{"name": "SYNO.DSM.Info", "method": "getinfo", "version": 2},
|
||||||
|
{"name": "SYNO.Core.System", "method": "info", "version": 1},
|
||||||
|
{"name": "SYNO.Core.System", "method": "info", "version": 2},
|
||||||
|
{"name": "SYNO.Core.System.Status", "method": "get", "version": 1},
|
||||||
|
{"name": "SYNO.Core.System.Status", "method": "get", "version": 2},
|
||||||
|
{"name": "SYNO.Core.System.Utilization", "method": "get", "version": 1},
|
||||||
|
{"name": "SYNO.Core.CurrentConnection", "method": "list", "version": 1}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Перебираем все API и тестируем их
|
||||||
|
for api in api_to_test:
|
||||||
|
# Сначала получаем информацию о конкретном API
|
||||||
|
try:
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": api["name"]
|
||||||
|
}
|
||||||
|
|
||||||
|
api_info_resp = session.get(api_info_url, params=api_info_params, timeout=10)
|
||||||
|
api_info_data = api_info_resp.json()
|
||||||
|
|
||||||
|
if api_info_data.get("success") and api["name"] in api_info_data.get("data", {}):
|
||||||
|
api_details = api_info_data["data"][api["name"]]
|
||||||
|
api_path = api_details.get("path", "entry.cgi")
|
||||||
|
api_min_version = api_details.get("minVersion", 1)
|
||||||
|
api_max_version = api_details.get("maxVersion", 1)
|
||||||
|
|
||||||
|
# Проверяем, поддерживается ли указанная версия
|
||||||
|
if api["version"] < api_min_version:
|
||||||
|
logger.warning(f"{api['name']} v{api['version']} ниже минимальной {api_min_version}, используем {api_min_version}")
|
||||||
|
test_version = api_min_version
|
||||||
|
elif api["version"] > api_max_version:
|
||||||
|
logger.warning(f"{api['name']} v{api['version']} выше максимальной {api_max_version}, используем {api_max_version}")
|
||||||
|
test_version = api_max_version
|
||||||
|
else:
|
||||||
|
test_version = api["version"]
|
||||||
|
|
||||||
|
# Выполняем запрос API
|
||||||
|
test_url = f"{base_url}/{api_path}"
|
||||||
|
test_params = {
|
||||||
|
"api": api["name"],
|
||||||
|
"version": str(test_version),
|
||||||
|
"method": api["method"],
|
||||||
|
"_sid": sid # Используем sid для аутентификации
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"Тестирование {api['name']}.{api['method']} v{test_version}")
|
||||||
|
test_response = session.get(test_url, params=test_params, timeout=10)
|
||||||
|
test_data = test_response.json()
|
||||||
|
|
||||||
|
if test_data.get("success"):
|
||||||
|
logger.info(f"API {api['name']}.{api['method']} v{test_version} РАБОТАЕТ!")
|
||||||
|
logger.info(f"Результат: {json.dumps(test_data.get('data', {}), indent=2)[:200]}...")
|
||||||
|
else:
|
||||||
|
error_code = test_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"API {api['name']}.{api['method']} v{test_version} ОШИБКА: {error_code}")
|
||||||
|
|
||||||
|
# Если ошибка связана с сессией, попробуем еще раз авторизоваться
|
||||||
|
if error_code == 119: # Session timeout
|
||||||
|
logger.info("Повторная авторизация из-за ошибки 119...")
|
||||||
|
|
||||||
|
# Создаем новую сессию
|
||||||
|
new_session = requests.Session()
|
||||||
|
new_session.verify = False
|
||||||
|
|
||||||
|
auth_response = new_session.get(auth_url, params=auth_params, timeout=10)
|
||||||
|
auth_data = auth_response.json()
|
||||||
|
|
||||||
|
if auth_data.get("success"):
|
||||||
|
new_sid = auth_data.get("data", {}).get("sid")
|
||||||
|
logger.info(f"Повторная авторизация успешна! Новый SID: {new_sid[:10]}...")
|
||||||
|
|
||||||
|
# Пробуем запрос с новым SID
|
||||||
|
test_params["_sid"] = new_sid
|
||||||
|
logger.info(f"Повторное тестирование {api['name']}.{api['method']} v{test_version}")
|
||||||
|
test_response = new_session.get(test_url, params=test_params, timeout=10)
|
||||||
|
test_data = test_response.json()
|
||||||
|
|
||||||
|
if test_data.get("success"):
|
||||||
|
logger.info(f"API {api['name']}.{api['method']} v{test_version} теперь РАБОТАЕТ!")
|
||||||
|
logger.info(f"Результат с новой сессией: {json.dumps(test_data.get('data', {}), indent=2)[:200]}...")
|
||||||
|
else:
|
||||||
|
error_code = test_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"API {api['name']}.{api['method']} v{test_version} ВСЕ ЕЩЕ С ОШИБКОЙ: {error_code}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"API {api['name']} не найден в информации API")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при тестировании {api['name']}.{api['method']} v{api['version']}: {str(e)}")
|
||||||
|
|
||||||
|
# Шаг 3: Тестирование комбинации запросов для решения проблемы
|
||||||
|
logger.info("Шаг 3: Тестирование комбинации запросов для решения проблемы")
|
||||||
|
|
||||||
|
# Создаем новую сессию для каждого запроса
|
||||||
|
for api in [{"name": "SYNO.DSM.Info", "method": "getinfo", "version": 1}]:
|
||||||
|
try:
|
||||||
|
fresh_session = requests.Session()
|
||||||
|
fresh_session.verify = False
|
||||||
|
|
||||||
|
# Авторизуемся
|
||||||
|
auth_response = fresh_session.get(auth_url, params=auth_params, timeout=10)
|
||||||
|
auth_data = auth_response.json()
|
||||||
|
|
||||||
|
if auth_data.get("success"):
|
||||||
|
fresh_sid = auth_data.get("data", {}).get("sid")
|
||||||
|
logger.info(f"Авторизация в новой сессии успешна! SID: {fresh_sid[:10]}...")
|
||||||
|
|
||||||
|
# Сразу же делаем запрос для получения информации в той же сессии
|
||||||
|
test_params = {
|
||||||
|
"api": api["name"],
|
||||||
|
"version": str(api["version"]),
|
||||||
|
"method": api["method"],
|
||||||
|
"_sid": fresh_sid
|
||||||
|
}
|
||||||
|
|
||||||
|
test_url = f"{base_url}/entry.cgi" # Используем entry.cgi по умолчанию
|
||||||
|
logger.info(f"Тест в свежей сессии: {api['name']}.{api['method']} v{api['version']}")
|
||||||
|
test_response = fresh_session.get(test_url, params=test_params, timeout=10)
|
||||||
|
test_data = test_response.json()
|
||||||
|
|
||||||
|
if test_data.get("success"):
|
||||||
|
logger.info(f"API в свежей сессии РАБОТАЕТ!")
|
||||||
|
logger.info(f"Результат: {json.dumps(test_data.get('data', {}), indent=2)[:200]}...")
|
||||||
|
else:
|
||||||
|
error_code = test_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"API в свежей сессии ОШИБКА: {error_code}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при тестировании свежей сессии: {str(e)}")
|
||||||
|
|
||||||
|
# Шаг 4: Получаем информацию об остальных API
|
||||||
|
logger.info("Шаг 4: Получаем информацию о доступных API для уточнения проблемы")
|
||||||
|
|
||||||
|
# Запрашиваем все API из SYNO.API.Info
|
||||||
|
try:
|
||||||
|
all_api_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
all_api_response = session.get(api_info_url, params=all_api_params, timeout=15) # Больший таймаут для большого ответа
|
||||||
|
all_api_data = all_api_response.json()
|
||||||
|
|
||||||
|
if all_api_data.get("success"):
|
||||||
|
api_list = all_api_data.get("data", {})
|
||||||
|
logger.info(f"Получен список всех API. Найдено {len(api_list)} API.")
|
||||||
|
|
||||||
|
# Ищем интересующие нас API для отладки
|
||||||
|
interested_in = ["SYNO.DSM.Info", "SYNO.Core.System", "SYNO.Core.Hardware",
|
||||||
|
"SYNO.Core.System.Status", "SYNO.API.Auth"]
|
||||||
|
|
||||||
|
logger.info("Информация о важных API:")
|
||||||
|
for api_name in interested_in:
|
||||||
|
if api_name in api_list:
|
||||||
|
logger.info(f"{api_name}: {api_list[api_name]}")
|
||||||
|
else:
|
||||||
|
logger.warning(f"API {api_name} не найден")
|
||||||
|
else:
|
||||||
|
logger.error("Не удалось получить список всех API")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при получении списка API: {str(e)}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
error_code = auth_data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Авторизация не удалась! Код ошибки: {error_code}")
|
||||||
|
else:
|
||||||
|
logger.error("Не удалось получить информацию об API авторизации")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Произошла ошибка при выполнении теста: {str(e)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logger.info("Запуск прямого теста API Synology")
|
||||||
|
direct_api_test()
|
||||||
21
.history/docker-compose_20250830102643.yml
Normal file
21
.history/docker-compose_20250830102643.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
synology-bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: synology-power-control-bot
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
# Если у вас есть файлы конфигурации или данные, которые нужно сохранять:
|
||||||
|
# - ./data:/app/data
|
||||||
|
networks:
|
||||||
|
- bot-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bot-network:
|
||||||
|
driver: bridge
|
||||||
36
.history/docker-compose_20250830102820.yml
Normal file
36
.history/docker-compose_20250830102820.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
synology-bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: synology-power-control-bot
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
# Если у вас есть файлы конфигурации или данные, которые нужно сохранять:
|
||||||
|
# - ./data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8080/health', timeout=5)"]
|
||||||
|
interval: 60s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
networks:
|
||||||
|
- bot-network
|
||||||
|
# Для ограничения ресурсов (раскомментируйте и настройте при необходимости):
|
||||||
|
# deploy:
|
||||||
|
# resources:
|
||||||
|
# limits:
|
||||||
|
# cpus: '0.50'
|
||||||
|
# memory: 512M
|
||||||
|
# reservations:
|
||||||
|
# cpus: '0.25'
|
||||||
|
# memory: 256M
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bot-network:
|
||||||
|
driver: bridge
|
||||||
38
.history/docker-compose_20250830102916.yml
Normal file
38
.history/docker-compose_20250830102916.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
synology-bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: synology-power-control-bot
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DOCKER_ENV=true
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
# Если у вас есть файлы конфигурации или данные, которые нужно сохранять:
|
||||||
|
# - ./data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8080/health', timeout=5)"]
|
||||||
|
interval: 60s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
networks:
|
||||||
|
- bot-network
|
||||||
|
# Для ограничения ресурсов (раскомментируйте и настройте при необходимости):
|
||||||
|
# deploy:
|
||||||
|
# resources:
|
||||||
|
# limits:
|
||||||
|
# cpus: '0.50'
|
||||||
|
# memory: 512M
|
||||||
|
# reservations:
|
||||||
|
# cpus: '0.25'
|
||||||
|
# memory: 256M
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bot-network:
|
||||||
|
driver: bridge
|
||||||
38
.history/docker-compose_20250830103154.yml
Normal file
38
.history/docker-compose_20250830103154.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
synology-bot:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: synology-power-control-bot
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- DOCKER_ENV=true
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
# Если у вас есть файлы конфигурации или данные, которые нужно сохранять:
|
||||||
|
# - ./data:/app/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8080/health', timeout=5)"]
|
||||||
|
interval: 60s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
networks:
|
||||||
|
- bot-network
|
||||||
|
# Для ограничения ресурсов (раскомментируйте и настройте при необходимости):
|
||||||
|
# deploy:
|
||||||
|
# resources:
|
||||||
|
# limits:
|
||||||
|
# cpus: '0.50'
|
||||||
|
# memory: 512M
|
||||||
|
# reservations:
|
||||||
|
# cpus: '0.25'
|
||||||
|
# memory: 256M
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bot-network:
|
||||||
|
driver: bridge
|
||||||
7
.history/entrypoint_20250830102747.sh
Normal file
7
.history/entrypoint_20250830102747.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Создаем директорию для логов, если она не существует
|
||||||
|
mkdir -p /app/logs
|
||||||
|
|
||||||
|
# Запускаем бота
|
||||||
|
exec python /app/run.py
|
||||||
7
.history/entrypoint_20250830103154.sh
Normal file
7
.history/entrypoint_20250830103154.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Создаем директорию для логов, если она не существует
|
||||||
|
mkdir -p /app/logs
|
||||||
|
|
||||||
|
# Запускаем бота
|
||||||
|
exec python /app/run.py
|
||||||
4
.history/requirements_20250830063740.txt
Normal file
4
.history/requirements_20250830063740.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
4
.history/requirements_20250830063839.txt
Normal file
4
.history/requirements_20250830063839.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
5
.history/requirements_20250830065002.txt
Normal file
5
.history/requirements_20250830065002.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
|
python-synology>=0.4.0
|
||||||
5
.history/requirements_20250830065455.txt
Normal file
5
.history/requirements_20250830065455.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
|
python-synology>=0.4.0
|
||||||
4
.history/requirements_20250830072350.txt
Normal file
4
.history/requirements_20250830072350.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
4
.history/requirements_20250830072817.txt
Normal file
4
.history/requirements_20250830072817.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
6
.history/requirements_20250830092418.txt
Normal file
6
.history/requirements_20250830092418.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
|
aiohttp>=3.8.4
|
||||||
|
async-timeout>=4.0.2
|
||||||
6
.history/requirements_20250830092441.txt
Normal file
6
.history/requirements_20250830092441.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
python-telegram-bot>=20.0
|
||||||
|
requests>=2.28.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
urllib3>=2.0.0
|
||||||
|
aiohttp>=3.8.4
|
||||||
|
async-timeout>=4.0.2
|
||||||
11
.history/run_20250830063754.py
Normal file
11
.history/run_20250830063754.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Точка входа для запуска телеграм-бота
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.bot import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
11
.history/run_20250830063839.py
Normal file
11
.history/run_20250830063839.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Точка входа для запуска телеграм-бота
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.bot import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
18
.history/run_20250830102904.py
Normal file
18
.history/run_20250830102904.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Точка входа для запуска телеграм-бота
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from src.bot import main
|
||||||
|
from src.healthcheck import start_health_server
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Запускаем healthcheck сервер в Docker-окружении
|
||||||
|
if os.environ.get("DOCKER_ENV", "False").lower() == "true":
|
||||||
|
start_health_server()
|
||||||
|
|
||||||
|
# Запускаем основной бот
|
||||||
|
main()
|
||||||
18
.history/run_20250830103154.py
Normal file
18
.history/run_20250830103154.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Точка входа для запуска телеграм-бота
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from src.bot import main
|
||||||
|
from src.healthcheck import start_health_server
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Запускаем healthcheck сервер в Docker-окружении
|
||||||
|
if os.environ.get("DOCKER_ENV", "False").lower() == "true":
|
||||||
|
start_health_server()
|
||||||
|
|
||||||
|
# Запускаем основной бот
|
||||||
|
main()
|
||||||
11
.history/run_bot_20250830082521.py
Normal file
11
.history/run_bot_20250830082521.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Файл-обёртка для запуска бота из корневой директории
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.bot import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
11
.history/run_bot_20250830082536.py
Normal file
11
.history/run_bot_20250830082536.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Файл-обёртка для запуска бота из корневой директории
|
||||||
|
"""
|
||||||
|
|
||||||
|
from src.bot import main
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
113
.history/src/api/api_discovery_20250830081819.py
Normal file
113
.history/src/api/api_discovery_20250830081819.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для обнаружения доступных API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
# Отключение предупреждений о небезопасных SSL-соединениях
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def discover_available_apis(base_url: str, timeout=(10, 20), verify=False) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Получение списка доступных API на Synology NAS
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: базовый URL для API (например, 'http://192.168.0.100:5000/webapi')
|
||||||
|
timeout: таймаут для запроса
|
||||||
|
verify: проверять ли SSL-сертификат
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с информацией о доступных API
|
||||||
|
"""
|
||||||
|
logger.info("Discovering available Synology APIs")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Делаем базовый запрос для получения всех доступных API
|
||||||
|
api_info_url = f"{base_url}/entry.cgi"
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
api_info_url,
|
||||||
|
params=api_info_params,
|
||||||
|
timeout=timeout,
|
||||||
|
verify=verify
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get("success"):
|
||||||
|
apis = data.get("data", {})
|
||||||
|
logger.info(f"Discovered {len(apis)} APIs")
|
||||||
|
|
||||||
|
# Выводим список найденных API
|
||||||
|
api_list = list(apis.keys())
|
||||||
|
logger.debug(f"Available APIs: {', '.join(api_list[:10])}... and {len(api_list) - 10} more")
|
||||||
|
|
||||||
|
# Группируем API по категориям
|
||||||
|
power_apis = [api for api in api_list if "power" in api.lower()]
|
||||||
|
system_apis = [api for api in api_list if "system" in api.lower()]
|
||||||
|
info_apis = [api for api in api_list if "info" in api.lower()]
|
||||||
|
|
||||||
|
logger.info(f"Power related APIs: {power_apis}")
|
||||||
|
logger.info(f"System related APIs: {system_apis[:10]}")
|
||||||
|
logger.info(f"Info related APIs: {info_apis[:10]}")
|
||||||
|
|
||||||
|
return apis
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to discover APIs. Error code: {error_code}")
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
logger.error(f"API discovery request failed with HTTP status: {response.status_code}")
|
||||||
|
return {}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during API discovery: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def find_compatible_api(apis: Dict[str, Any], api_category: str, method: str) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Поиск совместимых API заданной категории
|
||||||
|
|
||||||
|
Args:
|
||||||
|
apis: словарь с доступными API
|
||||||
|
api_category: категория API (например, 'system', 'power', 'info')
|
||||||
|
method: искомый метод API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список подходящих API с версиями
|
||||||
|
"""
|
||||||
|
compatible_apis = []
|
||||||
|
|
||||||
|
for api_name, api_info in apis.items():
|
||||||
|
if api_category.lower() in api_name.lower():
|
||||||
|
compatible_apis.append({
|
||||||
|
"name": api_name,
|
||||||
|
"path": api_info.get("path", "entry.cgi"),
|
||||||
|
"min_version": api_info.get("minVersion", 1),
|
||||||
|
"max_version": api_info.get("maxVersion", 1),
|
||||||
|
"method": method,
|
||||||
|
"version": api_info.get("maxVersion", 1) # Используем максимальную версию по умолчанию
|
||||||
|
})
|
||||||
|
|
||||||
|
# Сортируем по приоритету
|
||||||
|
compatible_apis.sort(key=lambda x: (
|
||||||
|
# Приоритет по точности совпадения категории
|
||||||
|
0 if api_category.upper() in x["name"] else 1,
|
||||||
|
# Приоритет по версии (от большей к меньшей)
|
||||||
|
-x["max_version"]
|
||||||
|
))
|
||||||
|
|
||||||
|
return compatible_apis
|
||||||
113
.history/src/api/api_discovery_20250830081957.py
Normal file
113
.history/src/api/api_discovery_20250830081957.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для обнаружения доступных API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import urllib3
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
# Отключение предупреждений о небезопасных SSL-соединениях
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def discover_available_apis(base_url: str, timeout=(10, 20), verify=False) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Получение списка доступных API на Synology NAS
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: базовый URL для API (например, 'http://192.168.0.100:5000/webapi')
|
||||||
|
timeout: таймаут для запроса
|
||||||
|
verify: проверять ли SSL-сертификат
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с информацией о доступных API
|
||||||
|
"""
|
||||||
|
logger.info("Discovering available Synology APIs")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Делаем базовый запрос для получения всех доступных API
|
||||||
|
api_info_url = f"{base_url}/entry.cgi"
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
api_info_url,
|
||||||
|
params=api_info_params,
|
||||||
|
timeout=timeout,
|
||||||
|
verify=verify
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
if data.get("success"):
|
||||||
|
apis = data.get("data", {})
|
||||||
|
logger.info(f"Discovered {len(apis)} APIs")
|
||||||
|
|
||||||
|
# Выводим список найденных API
|
||||||
|
api_list = list(apis.keys())
|
||||||
|
logger.debug(f"Available APIs: {', '.join(api_list[:10])}... and {len(api_list) - 10} more")
|
||||||
|
|
||||||
|
# Группируем API по категориям
|
||||||
|
power_apis = [api for api in api_list if "power" in api.lower()]
|
||||||
|
system_apis = [api for api in api_list if "system" in api.lower()]
|
||||||
|
info_apis = [api for api in api_list if "info" in api.lower()]
|
||||||
|
|
||||||
|
logger.info(f"Power related APIs: {power_apis}")
|
||||||
|
logger.info(f"System related APIs: {system_apis[:10]}")
|
||||||
|
logger.info(f"Info related APIs: {info_apis[:10]}")
|
||||||
|
|
||||||
|
return apis
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to discover APIs. Error code: {error_code}")
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
logger.error(f"API discovery request failed with HTTP status: {response.status_code}")
|
||||||
|
return {}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during API discovery: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def find_compatible_api(apis: Dict[str, Any], api_category: str, method: str) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Поиск совместимых API заданной категории
|
||||||
|
|
||||||
|
Args:
|
||||||
|
apis: словарь с доступными API
|
||||||
|
api_category: категория API (например, 'system', 'power', 'info')
|
||||||
|
method: искомый метод API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список подходящих API с версиями
|
||||||
|
"""
|
||||||
|
compatible_apis = []
|
||||||
|
|
||||||
|
for api_name, api_info in apis.items():
|
||||||
|
if api_category.lower() in api_name.lower():
|
||||||
|
compatible_apis.append({
|
||||||
|
"name": api_name,
|
||||||
|
"path": api_info.get("path", "entry.cgi"),
|
||||||
|
"min_version": api_info.get("minVersion", 1),
|
||||||
|
"max_version": api_info.get("maxVersion", 1),
|
||||||
|
"method": method,
|
||||||
|
"version": api_info.get("maxVersion", 1) # Используем максимальную версию по умолчанию
|
||||||
|
})
|
||||||
|
|
||||||
|
# Сортируем по приоритету
|
||||||
|
compatible_apis.sort(key=lambda x: (
|
||||||
|
# Приоритет по точности совпадения категории
|
||||||
|
0 if api_category.upper() in x["name"] else 1,
|
||||||
|
# Приоритет по версии (от большей к меньшей)
|
||||||
|
-x["max_version"]
|
||||||
|
))
|
||||||
|
|
||||||
|
return compatible_apis
|
||||||
234
.history/src/api/api_version_resolver_20250830084129.py
Normal file
234
.history/src/api/api_version_resolver_20250830084129.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для разрешения проблем с API Synology и автоматического выбора совместимых версий
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from typing import Dict, Any, Optional, List, Tuple
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ApiVersionResolver:
|
||||||
|
"""Класс для определения совместимых версий API и правильных методов"""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str, session: requests.Session, timeout: tuple = (10, 20)):
|
||||||
|
"""Инициализация класса ApiVersionResolver
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: Базовый URL API Synology NAS (например, http://192.168.0.102:5000/webapi)
|
||||||
|
session: Сессия requests для повторного использования соединений
|
||||||
|
timeout: Таймауты для запросов (connect_timeout, read_timeout)
|
||||||
|
"""
|
||||||
|
self.base_url = base_url
|
||||||
|
self.session = session
|
||||||
|
self.timeout = timeout
|
||||||
|
self.api_info_cache = {}
|
||||||
|
|
||||||
|
def get_api_info(self, api_name: str) -> Dict[str, Any]:
|
||||||
|
"""Получает информацию об API из SYNO.API.Info
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API для запроса (например, SYNO.DSM.Info)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict с информацией об API или пустой словарь в случае ошибки
|
||||||
|
"""
|
||||||
|
# Проверяем наличие данных в кэше
|
||||||
|
if api_name in self.api_info_cache:
|
||||||
|
return self.api_info_cache[api_name]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Запрос информации об API
|
||||||
|
api_info_url = f"{self.base_url}/entry.cgi"
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": api_name
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(f"Querying API info for {api_name}")
|
||||||
|
response = self.session.get(
|
||||||
|
api_info_url,
|
||||||
|
params=api_info_params,
|
||||||
|
timeout=self.timeout,
|
||||||
|
verify=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.warning(f"API info request failed with status {response.status_code}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success"):
|
||||||
|
logger.warning(f"API info request unsuccessful for {api_name}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Извлекаем информацию о запрошенном API
|
||||||
|
api_info = data.get("data", {}).get(api_name, {})
|
||||||
|
if not api_info:
|
||||||
|
logger.warning(f"API {api_name} not found in API info response")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Кэшируем результат
|
||||||
|
self.api_info_cache[api_name] = api_info
|
||||||
|
logger.debug(f"API info for {api_name}: {api_info}")
|
||||||
|
return api_info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error querying API info for {api_name}: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def resolve_api_path(self, api_name: str) -> str:
|
||||||
|
"""Определяет путь для API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Путь к API или 'entry.cgi' по умолчанию
|
||||||
|
"""
|
||||||
|
api_info = self.get_api_info(api_name)
|
||||||
|
return api_info.get("path", "entry.cgi")
|
||||||
|
|
||||||
|
def resolve_api_version(self, api_name: str, requested_version: int) -> int:
|
||||||
|
"""Определяет совместимую версию API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
requested_version: Запрошенная версия API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Совместимая версия API, которая будет работать
|
||||||
|
"""
|
||||||
|
api_info = self.get_api_info(api_name)
|
||||||
|
if not api_info:
|
||||||
|
# Если нет информации, возвращаем запрошенную версию
|
||||||
|
return requested_version
|
||||||
|
|
||||||
|
min_version = api_info.get("minVersion", 1)
|
||||||
|
max_version = api_info.get("maxVersion", requested_version)
|
||||||
|
|
||||||
|
# Проверка, поддерживается ли запрошенная версия
|
||||||
|
if requested_version < min_version:
|
||||||
|
logger.warning(f"API version {requested_version} for {api_name} is below minimum {min_version}, using {min_version}")
|
||||||
|
return min_version
|
||||||
|
elif requested_version > max_version:
|
||||||
|
logger.warning(f"API version {requested_version} for {api_name} exceeds maximum {max_version}, using {max_version}")
|
||||||
|
return max_version
|
||||||
|
|
||||||
|
return requested_version
|
||||||
|
|
||||||
|
def resolve_api_method(self, api_name: str) -> Dict[str, str]:
|
||||||
|
"""Определяет доступные методы для API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с типами методов и их правильными именами для данного API
|
||||||
|
"""
|
||||||
|
# Возможные методы для разных типов API
|
||||||
|
api_methods = {
|
||||||
|
# Методы для информации о системе
|
||||||
|
"SYNO.DSM.Info": {"info": "getinfo", "get": "getinfo"},
|
||||||
|
"SYNO.Core.System": {"info": "info", "get": "info"},
|
||||||
|
"SYNO.Core.System.Status": {"info": "get", "get": "get"},
|
||||||
|
"SYNO.Core.System.Info": {"info": "get", "get": "get"},
|
||||||
|
|
||||||
|
# Методы для управления питанием
|
||||||
|
"SYNO.Core.Hardware.PowerRecovery": {
|
||||||
|
"restart": "setPowerOnState",
|
||||||
|
"reboot": "setPowerOnState",
|
||||||
|
"shutdown": "setPowerOnState",
|
||||||
|
"poweroff": "setPowerOnState"
|
||||||
|
},
|
||||||
|
"SYNO.Core.System.Power": {
|
||||||
|
"restart": "restart",
|
||||||
|
"reboot": "restart",
|
||||||
|
"shutdown": "shutdown",
|
||||||
|
"poweroff": "shutdown"
|
||||||
|
},
|
||||||
|
"SYNO.DSM.Power": {
|
||||||
|
"restart": "reboot",
|
||||||
|
"reboot": "reboot",
|
||||||
|
"shutdown": "shutdown",
|
||||||
|
"poweroff": "shutdown"
|
||||||
|
},
|
||||||
|
"SYNO.Core.Hardware.NeedReboot": {
|
||||||
|
"restart": "reboot",
|
||||||
|
"reboot": "reboot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api_methods.get(api_name, {})
|
||||||
|
|
||||||
|
def get_api_special_params(self, api_name: str, method: str) -> Dict[str, Any]:
|
||||||
|
"""Возвращает специальные параметры, которые требуются для определенного API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
method: Метод API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с параметрами для метода или пустой словарь
|
||||||
|
"""
|
||||||
|
# Специфические параметры для определенных API
|
||||||
|
special_params = {
|
||||||
|
# Параметры для управления питанием
|
||||||
|
"SYNO.Core.Hardware.PowerRecovery": {
|
||||||
|
"setPowerOnState": {
|
||||||
|
"restart": {"reboot": "true"},
|
||||||
|
"reboot": {"reboot": "true"},
|
||||||
|
"shutdown": {"state": "powerbtn"},
|
||||||
|
"poweroff": {"state": "powerbtn"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Другие специальные параметры для других API
|
||||||
|
}
|
||||||
|
|
||||||
|
api_params = special_params.get(api_name, {})
|
||||||
|
method_params = api_params.get(method, {})
|
||||||
|
|
||||||
|
# Если это метод управления питанием, возвращаем соответствующие параметры
|
||||||
|
if isinstance(method_params, dict) and method in method_params:
|
||||||
|
return method_params[method]
|
||||||
|
|
||||||
|
return method_params
|
||||||
|
|
||||||
|
def find_compatible_api_for_function(self, function_type: str) -> List[Tuple[str, str, int]]:
|
||||||
|
"""Находит совместимые API для определенного типа функций
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_type: Тип функции ('info', 'power', 'status', etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список кортежей (api_name, method, version) в порядке приоритета
|
||||||
|
"""
|
||||||
|
# Определяем API для каждого типа функции
|
||||||
|
function_apis = {
|
||||||
|
"info": [
|
||||||
|
("SYNO.DSM.Info", "getinfo", 2),
|
||||||
|
("SYNO.Core.System", "info", 1),
|
||||||
|
("SYNO.Core.System.Status", "get", 1),
|
||||||
|
("SYNO.Core.System.Info", "get", 1)
|
||||||
|
],
|
||||||
|
"power_restart": [
|
||||||
|
("SYNO.Core.Hardware.PowerRecovery", "setPowerOnState", 1),
|
||||||
|
("SYNO.Core.Hardware.NeedReboot", "reboot", 1),
|
||||||
|
("SYNO.Core.System.Power", "restart", 1),
|
||||||
|
("SYNO.DSM.Power", "reboot", 1),
|
||||||
|
("SYNO.Core.System", "reboot", 3)
|
||||||
|
],
|
||||||
|
"power_shutdown": [
|
||||||
|
("SYNO.Core.Hardware.PowerRecovery", "setPowerOnState", 1),
|
||||||
|
("SYNO.Core.System.Power", "shutdown", 1),
|
||||||
|
("SYNO.DSM.Power", "shutdown", 1),
|
||||||
|
("SYNO.Core.System", "shutdown", 3)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return function_apis.get(function_type, [])
|
||||||
234
.history/src/api/api_version_resolver_20250830084257.py
Normal file
234
.history/src/api/api_version_resolver_20250830084257.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для разрешения проблем с API Synology и автоматического выбора совместимых версий
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from typing import Dict, Any, Optional, List, Tuple
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ApiVersionResolver:
|
||||||
|
"""Класс для определения совместимых версий API и правильных методов"""
|
||||||
|
|
||||||
|
def __init__(self, base_url: str, session: requests.Session, timeout: tuple = (10, 20)):
|
||||||
|
"""Инициализация класса ApiVersionResolver
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: Базовый URL API Synology NAS (например, http://192.168.0.102:5000/webapi)
|
||||||
|
session: Сессия requests для повторного использования соединений
|
||||||
|
timeout: Таймауты для запросов (connect_timeout, read_timeout)
|
||||||
|
"""
|
||||||
|
self.base_url = base_url
|
||||||
|
self.session = session
|
||||||
|
self.timeout = timeout
|
||||||
|
self.api_info_cache = {}
|
||||||
|
|
||||||
|
def get_api_info(self, api_name: str) -> Dict[str, Any]:
|
||||||
|
"""Получает информацию об API из SYNO.API.Info
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API для запроса (например, SYNO.DSM.Info)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict с информацией об API или пустой словарь в случае ошибки
|
||||||
|
"""
|
||||||
|
# Проверяем наличие данных в кэше
|
||||||
|
if api_name in self.api_info_cache:
|
||||||
|
return self.api_info_cache[api_name]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Запрос информации об API
|
||||||
|
api_info_url = f"{self.base_url}/entry.cgi"
|
||||||
|
api_info_params = {
|
||||||
|
"api": "SYNO.API.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "query",
|
||||||
|
"query": api_name
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(f"Querying API info for {api_name}")
|
||||||
|
response = self.session.get(
|
||||||
|
api_info_url,
|
||||||
|
params=api_info_params,
|
||||||
|
timeout=self.timeout,
|
||||||
|
verify=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.warning(f"API info request failed with status {response.status_code}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success"):
|
||||||
|
logger.warning(f"API info request unsuccessful for {api_name}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Извлекаем информацию о запрошенном API
|
||||||
|
api_info = data.get("data", {}).get(api_name, {})
|
||||||
|
if not api_info:
|
||||||
|
logger.warning(f"API {api_name} not found in API info response")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Кэшируем результат
|
||||||
|
self.api_info_cache[api_name] = api_info
|
||||||
|
logger.debug(f"API info for {api_name}: {api_info}")
|
||||||
|
return api_info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error querying API info for {api_name}: {str(e)}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def resolve_api_path(self, api_name: str) -> str:
|
||||||
|
"""Определяет путь для API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Путь к API или 'entry.cgi' по умолчанию
|
||||||
|
"""
|
||||||
|
api_info = self.get_api_info(api_name)
|
||||||
|
return api_info.get("path", "entry.cgi")
|
||||||
|
|
||||||
|
def resolve_api_version(self, api_name: str, requested_version: int) -> int:
|
||||||
|
"""Определяет совместимую версию API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
requested_version: Запрошенная версия API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Совместимая версия API, которая будет работать
|
||||||
|
"""
|
||||||
|
api_info = self.get_api_info(api_name)
|
||||||
|
if not api_info:
|
||||||
|
# Если нет информации, возвращаем запрошенную версию
|
||||||
|
return requested_version
|
||||||
|
|
||||||
|
min_version = api_info.get("minVersion", 1)
|
||||||
|
max_version = api_info.get("maxVersion", requested_version)
|
||||||
|
|
||||||
|
# Проверка, поддерживается ли запрошенная версия
|
||||||
|
if requested_version < min_version:
|
||||||
|
logger.warning(f"API version {requested_version} for {api_name} is below minimum {min_version}, using {min_version}")
|
||||||
|
return min_version
|
||||||
|
elif requested_version > max_version:
|
||||||
|
logger.warning(f"API version {requested_version} for {api_name} exceeds maximum {max_version}, using {max_version}")
|
||||||
|
return max_version
|
||||||
|
|
||||||
|
return requested_version
|
||||||
|
|
||||||
|
def resolve_api_method(self, api_name: str) -> Dict[str, str]:
|
||||||
|
"""Определяет доступные методы для API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с типами методов и их правильными именами для данного API
|
||||||
|
"""
|
||||||
|
# Возможные методы для разных типов API
|
||||||
|
api_methods = {
|
||||||
|
# Методы для информации о системе
|
||||||
|
"SYNO.DSM.Info": {"info": "getinfo", "get": "getinfo"},
|
||||||
|
"SYNO.Core.System": {"info": "info", "get": "info"},
|
||||||
|
"SYNO.Core.System.Status": {"info": "get", "get": "get"},
|
||||||
|
"SYNO.Core.System.Info": {"info": "get", "get": "get"},
|
||||||
|
|
||||||
|
# Методы для управления питанием
|
||||||
|
"SYNO.Core.Hardware.PowerRecovery": {
|
||||||
|
"restart": "setPowerOnState",
|
||||||
|
"reboot": "setPowerOnState",
|
||||||
|
"shutdown": "setPowerOnState",
|
||||||
|
"poweroff": "setPowerOnState"
|
||||||
|
},
|
||||||
|
"SYNO.Core.System.Power": {
|
||||||
|
"restart": "restart",
|
||||||
|
"reboot": "restart",
|
||||||
|
"shutdown": "shutdown",
|
||||||
|
"poweroff": "shutdown"
|
||||||
|
},
|
||||||
|
"SYNO.DSM.Power": {
|
||||||
|
"restart": "reboot",
|
||||||
|
"reboot": "reboot",
|
||||||
|
"shutdown": "shutdown",
|
||||||
|
"poweroff": "shutdown"
|
||||||
|
},
|
||||||
|
"SYNO.Core.Hardware.NeedReboot": {
|
||||||
|
"restart": "reboot",
|
||||||
|
"reboot": "reboot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return api_methods.get(api_name, {})
|
||||||
|
|
||||||
|
def get_api_special_params(self, api_name: str, method: str) -> Dict[str, Any]:
|
||||||
|
"""Возвращает специальные параметры, которые требуются для определенного API
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_name: Имя API
|
||||||
|
method: Метод API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с параметрами для метода или пустой словарь
|
||||||
|
"""
|
||||||
|
# Специфические параметры для определенных API
|
||||||
|
special_params = {
|
||||||
|
# Параметры для управления питанием
|
||||||
|
"SYNO.Core.Hardware.PowerRecovery": {
|
||||||
|
"setPowerOnState": {
|
||||||
|
"restart": {"reboot": "true"},
|
||||||
|
"reboot": {"reboot": "true"},
|
||||||
|
"shutdown": {"state": "powerbtn"},
|
||||||
|
"poweroff": {"state": "powerbtn"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
# Другие специальные параметры для других API
|
||||||
|
}
|
||||||
|
|
||||||
|
api_params = special_params.get(api_name, {})
|
||||||
|
method_params = api_params.get(method, {})
|
||||||
|
|
||||||
|
# Если это метод управления питанием, возвращаем соответствующие параметры
|
||||||
|
if isinstance(method_params, dict) and method in method_params:
|
||||||
|
return method_params[method]
|
||||||
|
|
||||||
|
return method_params
|
||||||
|
|
||||||
|
def find_compatible_api_for_function(self, function_type: str) -> List[Tuple[str, str, int]]:
|
||||||
|
"""Находит совместимые API для определенного типа функций
|
||||||
|
|
||||||
|
Args:
|
||||||
|
function_type: Тип функции ('info', 'power', 'status', etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список кортежей (api_name, method, version) в порядке приоритета
|
||||||
|
"""
|
||||||
|
# Определяем API для каждого типа функции
|
||||||
|
function_apis = {
|
||||||
|
"info": [
|
||||||
|
("SYNO.DSM.Info", "getinfo", 2),
|
||||||
|
("SYNO.Core.System", "info", 1),
|
||||||
|
("SYNO.Core.System.Status", "get", 1),
|
||||||
|
("SYNO.Core.System.Info", "get", 1)
|
||||||
|
],
|
||||||
|
"power_restart": [
|
||||||
|
("SYNO.Core.Hardware.PowerRecovery", "setPowerOnState", 1),
|
||||||
|
("SYNO.Core.Hardware.NeedReboot", "reboot", 1),
|
||||||
|
("SYNO.Core.System.Power", "restart", 1),
|
||||||
|
("SYNO.DSM.Power", "reboot", 1),
|
||||||
|
("SYNO.Core.System", "reboot", 3)
|
||||||
|
],
|
||||||
|
"power_shutdown": [
|
||||||
|
("SYNO.Core.Hardware.PowerRecovery", "setPowerOnState", 1),
|
||||||
|
("SYNO.Core.System.Power", "shutdown", 1),
|
||||||
|
("SYNO.DSM.Power", "shutdown", 1),
|
||||||
|
("SYNO.Core.System", "shutdown", 3)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return function_apis.get(function_type, [])
|
||||||
262
.history/src/api/synology_20250830063552.py
Normal file
262
.history/src/api/synology_20250830063552.py
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from src.config.config import (
|
||||||
|
SYNOLOGY_HOST,
|
||||||
|
SYNOLOGY_PORT,
|
||||||
|
SYNOLOGY_USERNAME,
|
||||||
|
SYNOLOGY_PASSWORD,
|
||||||
|
SYNOLOGY_SECURE,
|
||||||
|
SYNOLOGY_TIMEOUT,
|
||||||
|
SYNOLOGY_MAC,
|
||||||
|
WOL_PORT
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class SynologyAPI:
|
||||||
|
"""Класс для взаимодействия с API Synology NAS"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data")
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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()
|
||||||
262
.history/src/api/synology_20250830063839.py
Normal file
262
.history/src/api/synology_20250830063839.py
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from src.config.config import (
|
||||||
|
SYNOLOGY_HOST,
|
||||||
|
SYNOLOGY_PORT,
|
||||||
|
SYNOLOGY_USERNAME,
|
||||||
|
SYNOLOGY_PASSWORD,
|
||||||
|
SYNOLOGY_SECURE,
|
||||||
|
SYNOLOGY_TIMEOUT,
|
||||||
|
SYNOLOGY_MAC,
|
||||||
|
WOL_PORT
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class SynologyAPI:
|
||||||
|
"""Класс для взаимодействия с API Synology NAS"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data")
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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()
|
||||||
267
.history/src/api/synology_20250830065021.py
Normal file
267
.history/src/api/synology_20250830065021.py
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
#!/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 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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data")
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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()
|
||||||
315
.history/src/api/synology_20250830065110.py
Normal file
315
.history/src/api/synology_20250830065110.py
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
#!/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 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()
|
||||||
447
.history/src/api/synology_20250830065154.py
Normal file
447
.history/src/api/synology_20250830065154.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 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 []
|
||||||
447
.history/src/api/synology_20250830065454.py
Normal file
447
.history/src/api/synology_20250830065454.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 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 []
|
||||||
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 []
|
||||||
447
.history/src/api/synology_20250830071525.py
Normal file
447
.history/src/api/synology_20250830071525.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 []
|
||||||
446
.history/src/api/synology_20250830071727.py
Normal file
446
.history/src/api/synology_20250830071727.py
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
#!/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.dsm = None
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS используя python-synology"""
|
||||||
|
try:
|
||||||
|
# Создаем экземпляр SynologyDSM
|
||||||
|
self.dsm = SynologyDSM(
|
||||||
|
dsm_ip=SYNOLOGY_HOST,
|
||||||
|
dsm_port=SYNOLOGY_PORT,
|
||||||
|
username=SYNOLOGY_USERNAME,
|
||||||
|
password=SYNOLOGY_PASSWORD,
|
||||||
|
use_https=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 []
|
||||||
446
.history/src/api/synology_20250830071755.py
Normal file
446
.history/src/api/synology_20250830071755.py
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
#!/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.dsm = None
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS используя python-synology"""
|
||||||
|
try:
|
||||||
|
# Создаем экземпляр SynologyDSM
|
||||||
|
self.dsm = SynologyDSM(
|
||||||
|
dsm_ip=SYNOLOGY_HOST,
|
||||||
|
dsm_port=SYNOLOGY_PORT,
|
||||||
|
username=SYNOLOGY_USERNAME,
|
||||||
|
password=SYNOLOGY_PASSWORD,
|
||||||
|
use_https=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 []
|
||||||
445
.history/src/api/synology_20250830071934.py
Normal file
445
.history/src/api/synology_20250830071934.py
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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.dsm = None
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS используя python-synology"""
|
||||||
|
try:
|
||||||
|
# Создаем экземпляр SynologyDSM
|
||||||
|
self.dsm = SynologyDSM(
|
||||||
|
dsm_ip=SYNOLOGY_HOST,
|
||||||
|
dsm_port=SYNOLOGY_PORT,
|
||||||
|
username=SYNOLOGY_USERNAME,
|
||||||
|
password=SYNOLOGY_PASSWORD,
|
||||||
|
use_https=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 []
|
||||||
398
.history/src/api/synology_20250830072031.py
Normal file
398
.history/src/api/synology_20250830072031.py
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data")
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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 []
|
||||||
287
.history/src/api/synology_20250830072122.py
Normal file
287
.history/src/api/synology_20250830072122.py
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data")
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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_shared_folders(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение списка общих папок (заглушка)"""
|
||||||
|
logger.warning("Function get_shared_folders() is not implemented yet")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_system_load(self) -> Dict[str, Any]:
|
||||||
|
"""Получение информации о загрузке системы (заглушка)"""
|
||||||
|
logger.warning("Function get_system_load() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, Any]:
|
||||||
|
"""Получение подробной информации о хранилище (заглушка)"""
|
||||||
|
logger.warning("Function get_storage_status() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_security_status(self) -> Dict[str, bool]:
|
||||||
|
"""Получение информации о состоянии безопасности (заглушка)"""
|
||||||
|
logger.warning("Function get_security_status() is not implemented yet")
|
||||||
|
return {"success": False}
|
||||||
287
.history/src/api/synology_20250830072817.py
Normal file
287
.history/src/api/synology_20250830072817.py
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data")
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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_shared_folders(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение списка общих папок (заглушка)"""
|
||||||
|
logger.warning("Function get_shared_folders() is not implemented yet")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_system_load(self) -> Dict[str, Any]:
|
||||||
|
"""Получение информации о загрузке системы (заглушка)"""
|
||||||
|
logger.warning("Function get_system_load() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, Any]:
|
||||||
|
"""Получение подробной информации о хранилище (заглушка)"""
|
||||||
|
logger.warning("Function get_storage_status() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_security_status(self) -> Dict[str, bool]:
|
||||||
|
"""Получение информации о состоянии безопасности (заглушка)"""
|
||||||
|
logger.warning("Function get_security_status() is not implemented yet")
|
||||||
|
return {"success": False}
|
||||||
309
.history/src/api/synology_20250830073007.py
Normal file
309
.history/src/api/synology_20250830073007.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
# Если устройство недоступно, сразу возвращаем минимальную информацию
|
||||||
|
if not self.is_online():
|
||||||
|
logger.info("Device is offline, skipping API request")
|
||||||
|
return {"status": "offline"}
|
||||||
|
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
logger.warning("Not authenticated, returning minimal status")
|
||||||
|
return {"status": "unknown", "error": "authentication_failed"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data", {})
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
|
||||||
|
# Ошибка 104 - требуется авторизация или неверное API
|
||||||
|
if error_code == 104:
|
||||||
|
# Пробуем переавторизоваться
|
||||||
|
if self.login():
|
||||||
|
logger.info("Re-authenticated, trying again")
|
||||||
|
return self.get_system_status()
|
||||||
|
|
||||||
|
# Возвращаем минимальную информацию с ошибкой
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_code": error_code,
|
||||||
|
"is_online": True
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error getting status: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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_shared_folders(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение списка общих папок (заглушка)"""
|
||||||
|
logger.warning("Function get_shared_folders() is not implemented yet")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_system_load(self) -> Dict[str, Any]:
|
||||||
|
"""Получение информации о загрузке системы (заглушка)"""
|
||||||
|
logger.warning("Function get_system_load() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, Any]:
|
||||||
|
"""Получение подробной информации о хранилище (заглушка)"""
|
||||||
|
logger.warning("Function get_storage_status() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_security_status(self) -> Dict[str, bool]:
|
||||||
|
"""Получение информации о состоянии безопасности (заглушка)"""
|
||||||
|
logger.warning("Function get_security_status() is not implemented yet")
|
||||||
|
return {"success": False}
|
||||||
309
.history/src/api/synology_20250830073043.py
Normal file
309
.history/src/api/synology_20250830073043.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
# Если устройство недоступно, сразу возвращаем минимальную информацию
|
||||||
|
if not self.is_online():
|
||||||
|
logger.info("Device is offline, skipping API request")
|
||||||
|
return {"status": "offline"}
|
||||||
|
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
logger.warning("Not authenticated, returning minimal status")
|
||||||
|
return {"status": "unknown", "error": "authentication_failed"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data", {})
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
|
||||||
|
# Ошибка 104 - требуется авторизация или неверное API
|
||||||
|
if error_code == 104:
|
||||||
|
# Пробуем переавторизоваться
|
||||||
|
if self.login():
|
||||||
|
logger.info("Re-authenticated, trying again")
|
||||||
|
return self.get_system_status()
|
||||||
|
|
||||||
|
# Возвращаем минимальную информацию с ошибкой
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_code": error_code,
|
||||||
|
"is_online": True
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error getting status: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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_shared_folders(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение списка общих папок (заглушка)"""
|
||||||
|
logger.warning("Function get_shared_folders() is not implemented yet")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_system_load(self) -> Dict[str, Any]:
|
||||||
|
"""Получение информации о загрузке системы (заглушка)"""
|
||||||
|
logger.warning("Function get_system_load() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, Any]:
|
||||||
|
"""Получение подробной информации о хранилище (заглушка)"""
|
||||||
|
logger.warning("Function get_storage_status() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_security_status(self) -> Dict[str, bool]:
|
||||||
|
"""Получение информации о состоянии безопасности (заглушка)"""
|
||||||
|
logger.warning("Function get_security_status() is not implemented yet")
|
||||||
|
return {"success": False}
|
||||||
326
.history/src/api/synology_20250830073131.py
Normal file
326
.history/src/api/synology_20250830073131.py
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
# Если устройство недоступно, сразу возвращаем минимальную информацию
|
||||||
|
if not self.is_online():
|
||||||
|
logger.info("Device is offline, skipping API request")
|
||||||
|
return {"status": "offline"}
|
||||||
|
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
logger.warning("Not authenticated, returning minimal status")
|
||||||
|
return {"status": "unknown", "error": "authentication_failed"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data", {})
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
|
||||||
|
# Ошибка 104 - требуется авторизация или неверное API
|
||||||
|
if error_code == 104:
|
||||||
|
# Пробуем переавторизоваться
|
||||||
|
if self.login():
|
||||||
|
logger.info("Re-authenticated, trying again")
|
||||||
|
# Пробуем получить информацию еще раз, но без рекурсивного вызова
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
retry_params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_response = self.session.get(url, params=retry_params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
retry_data = retry_response.json()
|
||||||
|
|
||||||
|
if retry_data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status after re-authentication")
|
||||||
|
return retry_data.get("data", {})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during retry: {str(e)}")
|
||||||
|
|
||||||
|
# Возвращаем минимальную информацию с ошибкой
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_code": error_code,
|
||||||
|
"is_online": True
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error getting status: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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_shared_folders(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение списка общих папок (заглушка)"""
|
||||||
|
logger.warning("Function get_shared_folders() is not implemented yet")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_system_load(self) -> Dict[str, Any]:
|
||||||
|
"""Получение информации о загрузке системы (заглушка)"""
|
||||||
|
logger.warning("Function get_system_load() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, Any]:
|
||||||
|
"""Получение подробной информации о хранилище (заглушка)"""
|
||||||
|
logger.warning("Function get_storage_status() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_security_status(self) -> Dict[str, bool]:
|
||||||
|
"""Получение информации о состоянии безопасности (заглушка)"""
|
||||||
|
logger.warning("Function get_security_status() is not implemented yet")
|
||||||
|
return {"success": False}
|
||||||
326
.history/src/api/synology_20250830073142.py
Normal file
326
.history/src/api/synology_20250830073142.py
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
# Если устройство недоступно, сразу возвращаем минимальную информацию
|
||||||
|
if not self.is_online():
|
||||||
|
logger.info("Device is offline, skipping API request")
|
||||||
|
return {"status": "offline"}
|
||||||
|
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
logger.warning("Not authenticated, returning minimal status")
|
||||||
|
return {"status": "unknown", "error": "authentication_failed"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"sid": self.sid # Проверяем правильность параметра sid вместо _sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data", {})
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
|
||||||
|
# Ошибка 104 - требуется авторизация или неверное API
|
||||||
|
if error_code == 104:
|
||||||
|
# Пробуем переавторизоваться
|
||||||
|
if self.login():
|
||||||
|
logger.info("Re-authenticated, trying again")
|
||||||
|
# Пробуем получить информацию еще раз, но без рекурсивного вызова
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
retry_params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_response = self.session.get(url, params=retry_params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
retry_data = retry_response.json()
|
||||||
|
|
||||||
|
if retry_data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status after re-authentication")
|
||||||
|
return retry_data.get("data", {})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during retry: {str(e)}")
|
||||||
|
|
||||||
|
# Возвращаем минимальную информацию с ошибкой
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_code": error_code,
|
||||||
|
"is_online": True
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error getting status: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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_shared_folders(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение списка общих папок (заглушка)"""
|
||||||
|
logger.warning("Function get_shared_folders() is not implemented yet")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_system_load(self) -> Dict[str, Any]:
|
||||||
|
"""Получение информации о загрузке системы (заглушка)"""
|
||||||
|
logger.warning("Function get_system_load() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, Any]:
|
||||||
|
"""Получение подробной информации о хранилище (заглушка)"""
|
||||||
|
logger.warning("Function get_storage_status() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_security_status(self) -> Dict[str, bool]:
|
||||||
|
"""Получение информации о состоянии безопасности (заглушка)"""
|
||||||
|
logger.warning("Function get_security_status() is not implemented yet")
|
||||||
|
return {"success": False}
|
||||||
326
.history/src/api/synology_20250830073153.py
Normal file
326
.history/src/api/synology_20250830073153.py
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Модуль для взаимодействия с API Synology NAS
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
from time import sleep
|
||||||
|
import urllib3
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Инициализация класса SynologyAPI"""
|
||||||
|
self.protocol = "https" if SYNOLOGY_SECURE else "http"
|
||||||
|
self.base_url = f"{self.protocol}://{SYNOLOGY_HOST}:{SYNOLOGY_PORT}/webapi"
|
||||||
|
self.sid = None
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""Авторизация в API Synology NAS"""
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "3",
|
||||||
|
"method": "login",
|
||||||
|
"account": SYNOLOGY_USERNAME,
|
||||||
|
"passwd": SYNOLOGY_PASSWORD,
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"format": "cookie"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = data.get("data", {}).get("sid")
|
||||||
|
logger.info("Successfully logged in to Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log in to Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def logout(self) -> bool:
|
||||||
|
"""Выход из API Synology NAS"""
|
||||||
|
if not self.sid:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/auth.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.API.Auth",
|
||||||
|
"version": "1",
|
||||||
|
"method": "logout",
|
||||||
|
"session": "SynologyPowerControlBot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
self.sid = None
|
||||||
|
logger.info("Successfully logged out from Synology NAS")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to log out from Synology NAS: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_system_status(self) -> Optional[Dict[str, Any]]:
|
||||||
|
"""Получение статуса системы"""
|
||||||
|
# Если устройство недоступно, сразу возвращаем минимальную информацию
|
||||||
|
if not self.is_online():
|
||||||
|
logger.info("Device is offline, skipping API request")
|
||||||
|
return {"status": "offline"}
|
||||||
|
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
logger.warning("Not authenticated, returning minimal status")
|
||||||
|
return {"status": "unknown", "error": "authentication_failed"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"sid": self.sid # Проверяем правильность параметра sid вместо _sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status")
|
||||||
|
return data.get("data", {})
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to get system status: Error code {error_code}")
|
||||||
|
|
||||||
|
# Ошибка 104 - требуется авторизация или неверное API
|
||||||
|
if error_code == 104:
|
||||||
|
# Пробуем переавторизоваться
|
||||||
|
if self.login():
|
||||||
|
logger.info("Re-authenticated, trying again")
|
||||||
|
# Пробуем получить информацию еще раз, но без рекурсивного вызова
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
retry_params = {
|
||||||
|
"api": "SYNO.DSM.Info",
|
||||||
|
"version": "1",
|
||||||
|
"method": "getinfo",
|
||||||
|
"sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
retry_response = self.session.get(url, params=retry_params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
retry_data = retry_response.json()
|
||||||
|
|
||||||
|
if retry_data.get("success"):
|
||||||
|
logger.info("Successfully fetched system status after re-authentication")
|
||||||
|
return retry_data.get("data", {})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during retry: {str(e)}")
|
||||||
|
|
||||||
|
# Возвращаем минимальную информацию с ошибкой
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"error_code": error_code,
|
||||||
|
"is_online": True
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error getting status: {str(e)}")
|
||||||
|
return {"status": "error", "error": str(e), "is_online": True}
|
||||||
|
|
||||||
|
def shutdown_system(self) -> bool:
|
||||||
|
"""Выключение системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "shutdown",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system shutdown")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to shutdown system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reboot_system(self) -> bool:
|
||||||
|
"""Перезагрузка системы"""
|
||||||
|
if not self.sid and not self.login():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"{self.base_url}/entry.cgi"
|
||||||
|
params = {
|
||||||
|
"api": "SYNO.DSM.System",
|
||||||
|
"version": "1",
|
||||||
|
"method": "reboot",
|
||||||
|
"_sid": self.sid
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.session.get(url, params=params, timeout=SYNOLOGY_TIMEOUT, verify=False)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if data.get("success"):
|
||||||
|
logger.info("Successfully initiated system reboot")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = data.get("error", {}).get("code", -1)
|
||||||
|
logger.error(f"Failed to reboot system: Error code {error_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Connection error: {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_shared_folders(self) -> List[Dict[str, Any]]:
|
||||||
|
"""Получение списка общих папок (заглушка)"""
|
||||||
|
logger.warning("Function get_shared_folders() is not implemented yet")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_system_load(self) -> Dict[str, Any]:
|
||||||
|
"""Получение информации о загрузке системы (заглушка)"""
|
||||||
|
logger.warning("Function get_system_load() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_storage_status(self) -> Dict[str, Any]:
|
||||||
|
"""Получение подробной информации о хранилище (заглушка)"""
|
||||||
|
logger.warning("Function get_storage_status() is not implemented yet")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def get_security_status(self) -> Dict[str, bool]:
|
||||||
|
"""Получение информации о состоянии безопасности (заглушка)"""
|
||||||
|
logger.warning("Function get_security_status() is not implemented yet")
|
||||||
|
return {"success": False}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user