Мы можем использовать умную розетку с поддержкой управления по Wi-Fi (например, через приложение или API) для включения и отключения питания внешнего жесткого диска в заданное время.
План:
-
Выбрать умную розетку, которая позволяет управлять через API (например, TP-Link Kasa, Sonoff, Shelly) или хотя бы по расписанию.
-
Настроить расписание в приложении умной розетки, чтобы включать диск за несколько минут до запланированного бэкапа и выключать после.
-
Либо использовать скрипт (например, на Python) для управления розеткой через API, если нужно более точное управление.
Однако, есть нюансы:
-
Резкое отключение питания может повредить жесткий диск. Необходимо убедиться, что перед отключением питания все операции записи завершены и диск размонтирован (или безопасно извлечен в Windows).
-
Внешний жесткий диск может иметь собственный кэш, который не успеет записаться при резком отключении.
Поэтому лучше:
-
Перед отключением питания убедиться, что бэкап завершен и операционная система завершила все операции с диском.
-
Желательно использовать скрипт, который:
a) Включает розетку (или она включается по расписанию).
b) Ждет, пока диск появится в системе.
c) Запускает бэкап.
d) После завершения бэкапа безопасно извлекает диск (soft removal).
e) Отключает розетку.
Но умная розетка не может безопасно извлечь диск. Поэтому нужно программное безопасное извлечение перед отключением питания.
Если мы используем умную розетку только по расписанию, то нужно:
-
Рассчитать время, чтобы диск был включен до начала бэкапа и выключен после завершения, но без гарантии безопасного извлечения.
Альтернатива: использовать умную розетку в сочетании со скриптом, который управляет розеткой через API и выполняет безопасное извлечение.
Рассмотрим вариант с использованием TP-Link Kasa Smart Plug (HS110) и Python-библиотеки python-kasa.
Шаги:
-
Установить библиотеку:
pip install python-kasa -
Написать скрипт, который:
-
Включает розетку.
-
Ждет, пока диск смонтируется.
-
Запускает процесс бэкапа (например, с помощью robocopy или другой утилиты).
-
После завершения бэкапа, безопасно извлекает диск (в Windows: через
devconилиremove-diskв PowerShell). -
Отключает розетку.
-
Пример скрипта для Windows:
Примечание: Для безопасного извлечения диска в Windows можно использовать diskpart или PowerShell.
Однако, утилита devcon от Microsoft более надежна, но ее нужно скачать и установить.
Рассмотрим вариант с PowerShell для безопасного извлечения:
$driveEject = New-Object -comObject Shell.Application
$driveEject.Namespace(17).ParseName("E:").InvokeVerb("Eject")
Но это не всегда работает с внешними жесткими дисками.
Другой вариант: использовать Remove-Disk в PowerShell, но нужно знать номер диска.
Я предлагаю такой план скрипта:
-
Включить умную розетку.
-
Подождать, пока диск появится в системе (по букве диска или по метке).
-
Запустить бэкап (например, с помощью robocopy или любого другого средства).
-
После завершения бэкапа, безопасно извлечь диск с помощью командной строки или PowerShell.
-
Отключить умную розетку.
Пример скрипта на Python:
Предположим, что у нас есть умная розетка TP-Link Kasa с IP-адресом 192.168.1.100, а внешний жесткий диск имеет букву E:
Внимание: Этот скрипт должен запускаться с правами администратора, чтобы иметь возможность извлекать диск.
Однако, если мы используем программное извлечение, то после этого можно отключать питание.
Если скрипт работает на том же компьютере, который делает бэкап, то мы можем управлять розеткой и процессом бэкапа.
Но что, если бэкап делается с нескольких компьютеров на один внешний диск? Тогда лучше, чтобы розеткой управлял один компьютер, который инициирует бэкап.
Рассмотрим простой случай: один компьютер и один внешний диск.
Пример кода:
import asyncio
import subprocess
import time
from kasa import SmartPlug
async def control_plug(ip, turn_on):
plug = SmartPlug(ip)
await plug.update()
if turn_on:
await plug.turn_on()
else:
await plug.turn_off()
def wait_for_drive(letter, timeout=60):
"""Ждем, пока диск с заданной буквой появится в системе."""
start_time = time.time()
while time.time() - start_time < timeout:
try:
# Проверяем наличие диска в Windows
result = subprocess.run(["cmd", "/c", f"{letter}: && dir"], capture_output=True, text=True)
if result.returncode == 0:
return True
except:
pass
time.sleep(5)
return False
def run_backup(source, destination):
"""Запускаем бэкап с помощью robocopy."""
# Пример команды: robocopy source destination /MIR /R:3 /W:10
cmd = ["robocopy", source, destination, "/MIR", "/R:3", "/W:10", "/LOG:backup.log"]
result = subprocess.run(cmd, capture_output=True, text=True)
# robocopy возвращает коды ошибок, но 0-3 - это успех
if result.returncode <= 3:
return True
else:
print(f"Backup failed with return code {result.returncode}")
return False
def safe_remove_drive(letter):
"""Безопасно извлекаем диск по букве."""
# Используем PowerShell для извлечения
ps_script = f"""
$driveEject = New-Object -comObject Shell.Application
$driveEject.Namespace(17).ParseName("{letter}:").InvokeVerb("Eject")
"""
result = subprocess.run(["powershell", "-Command", ps_script], capture_output=True, text=True)
if result.returncode == 0:
# Дадим время на завершение операции извлечения
time.sleep(10)
return True
else:
# Попробуем альтернативный метод через diskpart
diskpart_script = f"""
select volume {letter}
remove all dismount
"""
with open("diskpart_script.txt", "w") as f:
f.write(diskpart_script)
result = subprocess.run(["diskpart", "/s", "diskpart_script.txt"], capture_output=True, text=True)
time.sleep(5)
return result.returncode == 0
async def main():
plug_ip = "192.168.1.100"
drive_letter = "E"
backup_source = "C:\\Users\\User\\Documents"
backup_dest = f"{drive_letter}:\\Backup"
# 1. Включаем розетку
print("Turning on the plug...")
await control_plug(plug_ip, True)
# 2. Ждем, пока диск появится
print("Waiting for the drive to appear...")
if not wait_for_drive(drive_letter):
print("Drive did not appear in time. Turning off the plug.")
await control_plug(plug_ip, False)
return
# 3. Запускаем бэкап
print("Starting backup...")
if not run_backup(backup_source, backup_dest):
print("Backup failed. Please check the log.")
# Не выключаем розетку сразу, может, нужно оставить диск для диагностики?
# Решаем по обстоятельствам. Можно оставить включенным и вывести сообщение.
return
# 4. Безопасно извлекаем диск
print("Safely removing the drive...")
if not safe_remove_drive(drive_letter):
print("Failed to safely remove the drive. Turning off the plug anyway.")
# Можно попробовать подождать и повторить, но в данном случае просто выключаем
# 5. Отключаем розетку
print("Turning off the plug...")
await control_plug(plug_ip, False)
if __name__ == "__main__":
asyncio.run(main())
Этот скрипт нужно адаптировать под вашу ситуацию.
Важные замечания:
-
Убедитесь, что умная розетка поддерживает API и у вас есть ее IP-адрес.
-
Возможно, потребуется настроить сеть так, чтобы компьютер мог управлять розеткой.
-
Скрипт должен запускаться с правами администратора, чтобы иметь возможность извлекать диск.
-
Внешний жесткий диск должен быть отформатирован в файловую систему, которую поддерживает Windows (NTFS, exFAT и т.д.).
-
Бэкап делается с помощью robocopy, но вы можете заменить на любую другую утилиту (например, 7-Zip, rsync для Windows, или вашу любимую программу бэкапа).
Альтернатива: использовать программу бэкапа, которая умеет выполнять пред- и пост-скрипты. Тогда можно в пред-скрипте включить розетку, а в пост-скрипте извлечь диск и отключить розетку.
Если вы используете программу бэкапа с поддержкой скриптов, то можно упростить скрипт, оставив только управление розеткой и ожидание диска, а бэкап делать самой программой.
В любом случае, эта концепция позволяет физически отключать диск после бэкапа, что защищает его от ransomware, пока диск отключен.
Однако, помните, что если ransomware уже в системе и запустится в момент, когда диск подключен, то он может его зашифровать. Поэтому важно также использовать другие меры безопасности.
Дополнительно: можно использовать умную розетку, которая поддерживает работу по расписанию без скрипта, но тогда безопасное извлечение диска нужно будет делать как-то иначе (например, с помощью планировщика задач, который запускает безопасное извлечение перед отключением розетки).
В заключение, предложенный скрипт — это база, которую можно дорабатывать под свои нужды.
Умная розетка (Wi-Fi розетка) — это простой и эффективный способ физической изоляции жесткого диска от сети. Вот подробное руководство по реализации этой схемы:
1. Выбор умной розетки
Критерии выбора:
-
Поддержка API или интеграция с Home Assistant/Node-RED
-
Мощность: минимум 50-60Вт (HDD потребляет ~10-20Вт, но нужен запас)
-
Локальное управление (не только через облако)
-
Таймер/расписание в приложении
Популярные модели:
| Модель | Особенности | API |
|---|---|---|
| Shelly Plug S | Локальное управление, HTTP API, мониторинг потребления | ✅ HTTP/MQTT |
| Sonoff S31 | Дешевая, перепрошивается на Tasmota | ✅ (после прошивки) |
| TP-Link Tapo P110 | Мониторинг энергии, стабильное приложение | ✅ (через cloud API) |
| Xiaomi Mi Smart Plug | Недорогая, но китайский регион | ⚠️ Сложный API |
| Tuya-based розетки | Много вариантов, нужна перепрошивка | ✅ (с Tuya-convert) |
Рекомендация: Shelly Plug S — лучший баланс цены, качества и открытости API.
2. Схема подключения и настройки
Оборудование:
Розетка 220В → Умная розетка → Блок питания HDD → Внешний HDD
(Wi-Fi управление)
↓
Локальная сеть ← ПК с ПО бэкапа
Настройка Shelly Plug S:
-
Подключите через приложение Shelly
-
Включите HTTP API в настройках:
http://<IP-адрес-розетки>/settings?apikey=<ваш-ключ> -
Настройте статический IP или резервацию DHCP
3. Скрипты для автоматизации
Вариант A: PowerShell скрипт для Windows
# backup_with_smartplug.ps1
param(
[string]$ShellyIP = "192.168.1.100",
[string]$BackupDriveLetter = "E",
[string]$BackupSource = "C:\ImportantData",
[string]$BackupDest = "E:\Backup",
[string]$BackupTool = "robocopy"
)
# Функция управления розеткой Shelly
function Control-ShellyPlug {
param([string]$Action, [string]$IP)
$url = "http://$IP/relay/0?turn=$Action"
try {
$response = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec 10
return $response.ison
} catch {
Write-Error "Ошибка управления розеткой: $_"
return $null
}
}
# Функция ожидания появления диска
function Wait-ForDrive {
param([string]$Letter, [int]$Timeout = 120)
$endTime = (Get-Date).AddSeconds($Timeout)
while ((Get-Date) -lt $endTime) {
if (Test-Path "${Letter}:\\") {
# Дополнительная проверка, что диск готов
$drive = Get-WmiObject Win32_LogicalDisk | Where-Object {$_.DeviceID -eq "${Letter}:"}
if ($drive -and $drive.Size -gt 0) {
Start-Sleep -Seconds 5 # Даем время на инициализацию
return $true
}
}
Start-Sleep -Seconds 2
}
return $false
}
# Функция безопасного извлечения
function Safe-RemoveDrive {
param([string]$Letter)
# Отключаем букву диска
$drive = Get-WmiObject Win32_LogicalDisk | Where-Object {$_.DeviceID -eq "${Letter}:"}
if ($drive) {
$driveEject = New-Object -comObject Shell.Application
$driveEject.Namespace(17).ParseName("${Letter}:").InvokeVerb("Eject")
Start-Sleep -Seconds 5
}
}
# Основной процесс
try {
Write-Host "=== Начало процедуры бэкапа ===" -ForegroundColor Green
# 1. Включаем розетку
Write-Host "Включаю питание HDD..." -ForegroundColor Yellow
$result = Control-ShellyPlug -Action "on" -IP $ShellyIP
if ($result -ne $true) { throw "Не удалось включить розетку" }
# 2. Ждем появления диска (до 2 минут)
Write-Host "Ожидание инициализации диска ${BackupDriveLetter}:..." -ForegroundColor Yellow
if (-not (Wait-ForDrive -Letter $BackupDriveLetter -Timeout 120)) {
throw "Диск не появился в системе"
}
# 3. Выполняем бэкап
Write-Host "Запуск бэкапа..." -ForegroundColor Green
# Пример с Robocopy
if ($BackupTool -eq "robocopy") {
$logFile = "C:\BackupLogs\backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$cmdArgs = "`"$BackupSource`" `"$BackupDest`" /MIR /Z /R:3 /W:5 /TEE /LOG:`"$logFile`" /NP"
Write-Host "Выполняю: robocopy $cmdArgs"
Start-Process "robocopy.exe" -ArgumentList $cmdArgs -Wait -NoNewWindow
}
# Или ваша программа бэкапа
# 4. Безопасно извлекаем диск
Write-Host "Безопасное извлечение диска..." -ForegroundColor Yellow
Safe-RemoveDrive -Letter $BackupDriveLetter
Start-Sleep -Seconds 10 # Ждем завершения операций
# 5. Выключаем розетку
Write-Host "Отключаю питание HDD..." -ForegroundColor Yellow
$result = Control-ShellyPlug -Action "off" -IP $ShellyIP
if ($result -ne $false) { Write-Warning "Розетка могла не выключиться" }
Write-Host "=== Бэкап успешно завершен ===" -ForegroundColor Green
} catch {
Write-Error "Ошибка: $_"
# В случае ошибки пытаемся выключить розетку
Control-ShellyPlug -Action "off" -IP $ShellyIP | Out-Null
exit 1
}
Вариант B: Python скрипт (кроссплатформенный)
#!/usr/bin/env python3
import requests
import time
import subprocess
import os
import sys
from pathlib import Path
class SmartPlugBackup:
def __init__(self, plug_ip, drive_path):
self.plug_ip = plug_ip
self.drive_path = Path(drive_path)
def plug_control(self, state):
"""Управление розеткой Shelly"""
url = f"http://{self.plug_ip}/relay/0"
data = {"turn": state}
try:
response = requests.get(url, params=data, timeout=10)
return response.json()['ison']
except Exception as e:
print(f"Ошибка управления розеткой: {e}")
return None
def wait_for_drive(self, timeout=120):
"""Ожидание появления диска"""
start = time.time()
while time.time() - start < timeout:
if self.drive_path.exists():
# Проверяем доступность для записи
test_file = self.drive_path / ".write_test"
try:
test_file.touch()
test_file.unlink()
time.sleep(3) # Дополнительная пауза
return True
except:
pass
time.sleep(2)
return False
def run_backup(self):
"""Выполнение бэкапа"""
# Пример: rsync для Linux/Mac
source = "/home/user/important/"
dest = str(self.drive_path / "backup/")
cmd = ["rsync", "-avz", "--delete", source, dest]
print(f"Выполняю: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("Бэкап успешен")
return True
else:
print(f"Ошибка бэкапа: {result.stderr}")
return False
def safe_unmount(self):
"""Безопасное размонтирование (Linux/Mac)"""
if sys.platform == "linux":
cmd = ["udisksctl", "unmount", "-b", self.drive_path]
subprocess.run(cmd, capture_output=True)
elif sys.platform == "darwin":
cmd = ["diskutil", "unmount", self.drive_path]
subprocess.run(cmd, capture_output=True)
def execute(self):
"""Основной процесс"""
print("=== Начинаем процедуру бэкапа ===")
try:
# 1. Включаем розетку
print("Включаем питание HDD...")
if self.plug_control("on") is not True:
raise Exception("Не удалось включить розетку")
# 2. Ждем диск
print("Ожидаем инициализацию диска...")
if not self.wait_for_drive():
raise Exception("Диск не появился")
# 3. Выполняем бэкап
print("Выполняем бэкап...")
if not self.run_backup():
raise Exception("Ошибка при бэкапе")
# 4. Размонтируем диск
print("Безопасное размонтирование...")
self.safe_unmount()
time.sleep(5)
# 5. Выключаем розетку
print("Отключаем питание HDD...")
self.plug_control("off")
print("=== Бэкап завершен успешно ===")
except Exception as e:
print(f"Ошибка: {e}")
# Аварийное отключение
self.plug_control("off")
sys.exit(1)
if __name__ == "__main__":
# Настройки
plug = SmartPlugBackup(
plug_ip="192.168.1.100",
drive_path="/mnt/backup_disk" # или "E:" для Windows
)
plug.execute()
4. Интеграция с ПО бэкапа
Для Veeam Agent:
-
Создайте скрипт
pre-job.sh/pre-job.ps1:
# Veeam pre-job script
curl "http://192.168.1.100/relay/0?turn=on"
timeout /t 60 /nobreak
-
Создайте скрипт
post-job.sh/post-job.ps1:
# Veeam post-job script
# Безопасное извлечение
$driveEject = New-Object -comObject Shell.Application
$driveEject.Namespace(17).ParseName("E:").InvokeVerb("Eject")
timeout /t 10 /nobreak
# Выключение розетки
curl "http://192.168.1.100/relay/0?turn=off"
Для Duplicati:
В настройках бэкапа:
--run-script-before = /path/to/script/start_backup.sh
--run-script-after = /path/to/script/end_backup.sh
5. Расписание через Планировщик задач (Windows)
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Triggers>
<CalendarTrigger>
<StartBoundary>2024-01-20T02:00:00</StartBoundary>
<ExecutionTimeLimit>PT4H</ExecutionTimeLimit>
<Enabled>true</Enabled>
<ScheduleByWeek>
<DaysOfWeek>
<Sunday />
<Wednesday />
</DaysOfWeek>
<WeeksInterval>1</WeeksInterval>
</ScheduleByWeek>
</CalendarTrigger>
</Triggers>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-ExecutionPolicy Bypass -File "C:\Scripts\backup_with_smartplug.ps1"</Arguments>
</Exec>
</Actions>
</Task>
6. Альтернатива: Управление через Home Assistant
Если у вас есть Home Assistant:
# automation.yaml
automation:
- alias: "Backup Automation"
trigger:
platform: time
at: "02:00:00"
action:
- service: switch.turn_on
target:
entity_id: switch.shelly_plug_hdd
- delay:
minutes: 2
- service: shell_command.start_backup
- wait_for_trigger:
platform: state
entity_id: sensor.backup_status
to: "completed"
timeout: "03:00:00"
continue_on_timeout: false
- service: switch.turn_off
target:
entity_id: switch.shelly_plug_hdd
7. Важные предупреждения и советы
⚠️ Критически важные моменты:
-
Задержки обязательны:
-
После включения: 30-60 секунд для инициализации HDD
-
Перед выключением: 10-15 секунд после безопасного извлечения
-
-
Защита от сбоев:
# Добавьте в скрипт проверку свободного места
$freeSpace = (Get-PSDrive $BackupDriveLetter).Free
if ($freeSpace -lt 10GB) {
Write-Error "Недостаточно места на диске"
Control-ShellyPlug -Action "off" -IP $ShellyIP
exit 1
}
-
Мониторинг:
# Отправка уведомлений
if (Test-Connection -ComputerName $ShellyIP -Count 2 -Quiet) {
Write-Host "Розетка онлайн"
} else {
Send-MailMessage -To "Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. " -Subject "Ошибка бэкапа" -Body "Розетка не отвечает"
}
-
Аппаратные риски:
-
Используйте ИБП для компьютера и розетки
-
Не подключайте через умную розетку жесткие диски с шифрованием (не успеют разблокироваться)
-
Проверьте, что блок питания HDD выдерживает частые включения/выключения
-
Плюсы решения:
-
✅ Физическая изоляция от ransomware
-
✅ Нет нагрузки на USB-порты компьютера
-
✅ Можно использовать для нескольких HDD через разветвитель
-
✅ Управление по расписанию из любого места
Минусы:
-
❌ Не защищает от ransomware, активного во время бэкапа
-
❌ Дополнительная точка отказа (Wi-Fi сеть)
-
❌ Сокращение срока службы HDD из-за частых включений/выключений
8. Готовые решения
-
Yottamaster Drive Box — бокс для HDD с аппаратным выключателем
-
ORICO 2.5/3.5 Hard Drive Enclosure с внешним питанием и кнопкой
-
Mediasonic HFR2-SU3S2 — 4-х отсечный бокс с индивидуальными выключателями
Рекомендация:
Для максимальной безопасности комбинируйте:
-
Умную розетку для физического отключения
-
Скрипт безопасного извлечения перед отключением
-
Облачный бэкап как второй уровень
-
Антивирус с защитой от ransomware
Это решение особенно эффективно для архивных бэкапов, которые делаются раз в неделю/месяц, а не для ежедневных инкрементальных копий.