init commit

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

59
.dockerignore Normal file
View 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
View 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
View 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

View 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/

View 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/

View 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/

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
- Ограничьте доступ к боту только доверенным пользователям
- Рассмотрите возможность запуска на выделенной сети или с дополнительными ограничениями доступа

View 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
- Ограничьте доступ к боту только доверенным пользователям
- Рассмотрите возможность запуска на выделенной сети или с дополнительными ограничениями доступа

View 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"]

View 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"]

View 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"]

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
```

View 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
```

View 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}"

View 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

View 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

View 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}"

View 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()

View 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()

View 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()

View 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()

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Создаем директорию для логов, если она не существует
mkdir -p /app/logs
# Запускаем бота
exec python /app/run.py

View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Создаем директорию для логов, если она не существует
mkdir -p /app/logs
# Запускаем бота
exec python /app/run.py

View File

@@ -0,0 +1,4 @@
python-telegram-bot>=20.0
requests>=2.28.0
python-dotenv>=1.0.0
urllib3>=2.0.0

View File

@@ -0,0 +1,4 @@
python-telegram-bot>=20.0
requests>=2.28.0
python-dotenv>=1.0.0
urllib3>=2.0.0

View 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

View 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

View File

@@ -0,0 +1,4 @@
python-telegram-bot>=20.0
requests>=2.28.0
python-dotenv>=1.0.0
urllib3>=2.0.0

View File

@@ -0,0 +1,4 @@
python-telegram-bot>=20.0
requests>=2.28.0
python-dotenv>=1.0.0
urllib3>=2.0.0

View 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

View 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

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Точка входа для запуска телеграм-бота
"""
from src.bot import main
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Точка входа для запуска телеграм-бота
"""
from src.bot import main
if __name__ == "__main__":
main()

View 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()

View 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()

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Файл-обёртка для запуска бота из корневой директории
"""
from src.bot import main
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Файл-обёртка для запуска бота из корневой директории
"""
from src.bot import main
if __name__ == "__main__":
main()

View 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

View 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

View 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, [])

View 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, [])

View 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()

View 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()

View 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()

View 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()

View File

@@ -0,0 +1,447 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Модуль для взаимодействия с API Synology NAS с использованием библиотеки python-synology
"""
import requests
import json
import logging
from typing import Dict, Any, Optional, List, Tuple
import socket
import struct
from time import sleep
import urllib3
from synology 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 []

View File

@@ -0,0 +1,447 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Модуль для взаимодействия с API Synology NAS с использованием библиотеки python-synology
"""
import requests
import json
import logging
from typing import Dict, Any, Optional, List, Tuple
import socket
import struct
from time import sleep
import urllib3
from synology 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 []

View File

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

View File

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

View 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 []

View 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 []

View 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 []

View 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 []

View 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}

View 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}

View 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}

View 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}

View 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}

View 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}

View 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