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