Вайбкодерские идеи для домашнего сервера: 5 практических приложений
Соберите полезные сервисы на собственном сервере уже сегодня: медиаконтент, личный облачный хостинг, мониторинг, автоматизация и AI-ассистент.
Медиа-центр с интеллектуальными рекомендациями
В домашней лаборатории я часто использую Plex как медиаплеер. Сам по себе Plex умеет транскодировать, хранить субтитры и отдавать контент по LAN/Internet, но без рекомендаций он напоминает обычный медиаплеер. Добавим к нему слой «умного» поиска — будем запрашивать TMDB API, кэшировать результаты в Redis и подсовывать их Plex через web-hooks. Всё это удобно собрать в Docker-Compose, а управление транскодировкой и субтитрами делаем через plextranscoder-контейнер.
Во-первых, убедитесь, что ваш сервер (у меня «Винни-Пух») имеет открытый порт 32400 и статический IP в локальной сети.
Во-вторых, подготовьте отдельный том/mnt/media— в нём будут лежать фильмы и сериалы. На TrueNAS это NFS-шар, экспортированный какmedia.
Развёртывание Plex + интеграция с TMDB API
- Создайте файл
.env– в нём храним токен TMDB и параметры Redis.
dockerfile# .env TMDB_API_KEY=your_tmdb_api_key_here REDIS_HOST=redis REDIS_PORT=6379
- docker-compose.yml – три сервиса: Plex, Redis и небольшое приложение-посредник
tmdb-proxy.
yamlversion: "3.9"
services:
plex:
image: linuxserver/plex:latest
container_name: plex
network_mode: host
environment:
- PUID=1000
- PGID=1000
- VERSION=docker
volumes:
- /mnt/media:/data
- /opt/plex/config:/config
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: redis
command: ["redis-server", "--save", "60", "1"]
volumes:
- redis-data:/data
restart: unless-stopped
tmdb-proxy:
image: python:3.12-slim
container_name: tmdb-proxy
working_dir: /app
environment:
- TMDB_API_KEY=${TMDB_API_KEY}
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
volumes:
- ./tmdb-proxy:/app
command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
restart: unless-stopped
depends_on:
- redis
volumes:
redis-data:
- Код
tmdb-proxy(папкаtmdb-proxy). Простейший FastAPI-сервис, который получает запрос от Plex-вебхука, ищет фильм в TMDB, кэширует результат в Redis и отсылает обратно JSON-пейлоады.
python# tmdb-proxy/main.py
import os
import aiohttp
import aioredis
from fastapi import FastAPI, Request
app = FastAPI()
redis = None
@app.on_event("startup")
async def startup():
global redis
redis = await aioredis.from_url(
f"redis://{os.getenv('REDIS_HOST')}:{os.getenv('REDIS_PORT')}"
)
@app.get("/recommend")
async def recommend(title: str):
cache_key = f"tmdb:{title.lower()}"
cached = await redis.get(cache_key)
if cached:
return {"source": "cache", "data": cached.decode()}
async with aiohttp.ClientSession() as session:
params = {
"api_key": os.getenv("TMDB_API_KEY"),
"query": title,
"language": "ru-RU"
}
async with session.get(
"https://api.themoviedb.org/3/search/movie", params=params
) as resp:
data = await resp.json()
await redis.set(cache_key, str(data), ex=86400) # кэш на сутки
return {"source": "api", "data": data}
- Подключаем webhook в Plex: в веб-интерфейсе Plex → Settings → Webhooks добавить URL
http://<IP_сервера>:8000/recommend?title={title}. При каждом запросе Plex передаст название, а наш сервис вернёт рекомендации, которые можно отобразить в клиенте через пользовательскую панель (например, с помощьюplexpy-плагина).
С практической точки зрения, такой подход даёт предсказуемость: если TMDB недоступен, Redis отдает старый результат, а Plex продолжает работать без задержек.
Docker-Compose для автоматической транскодировки и субтитров
Для домашнего кластера я использую plextranscoder-контейнер, который следит за новыми медиа-файлами, запускает ffmpeg-транскодинг в нужный профиль и скачивает субтитры из OpenSubtitles. Всё это тоже укладывается в один docker-compose.yml.
- Дополнительный сервис в том же файле (добавляем после
tmdb-proxy):
yaml transcoder:
image: ghcr.io/jellyfin/jellyfin-ffmpeg:latest
container_name: plex-transcoder
volumes:
- /mnt/media:/media
- ./transcode-scripts:/scripts
environment:
- TZ=Europe/Moscow
command: ["/scripts/watch.sh"]
restart: unless-stopped
depends_on:
- plex
- Скрипт
watch.sh– наблюдает за каталогом/mediaи при появлении нового файла запускаетffmpeg.
bash#!/usr/bin/env bash
inotifywait -m -e close_write --format '%w%f' /media | while read FILE; do
echo "Новый файл: $FILE"
# Если файл > 4 ГБ, делаем 1080p-транскод
if [[ $(stat -c%s "$FILE") -gt $((4*1024*1024*1024)) ]]; then
out="${FILE%.*}_1080p.mkv"
ffmpeg -i "$FILE" -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 192k "$out"
echo "Транскод завершён: $out"
fi
# Скачиваем субтитры (OpenSubtitles API)
title=$(basename "$FILE" | sed -E 's/[\._].*//')
curl -s "https://api.opensubtitles.com/api/v1/subtitles?query=${title}&languages=ru" \
-H "Api-Key: your_opensubtitles_key" \
-o "/media/${title}.srt"
done
Важно: перед запуском установите права выполнения
chmod +x watch.shи проверьте, что пользователь внутри контейнера имеет доступ к/mnt/media.
- Запуск
bashdocker compose up -d plex redis tmdb-proxy transcoder
После старта все новые фильмы автоматически транскодируются в 1080p (если исходный размер превышает 4 ГБ) и снабжаются русскими субтитрами. Plex сразу их «видит», а рекомендации от TMDB уже находятся в кэше, поэтому пользователь получает персонализированный список похожих фильмов без дополнительных кликов.
Вывод
Сочетание Plex, TMDB-проكسي и автоматического транскодера в одном docker-compose-стеке даёт предсказуемую, масштабируемую и полностью контролируемую медиаплатформу. Всё работает без GUI-инструментов, а конфиги находятся в обычных текстовых файлах – так легче поддерживать и документировать. При необходимости добавить новые сервисы (например, Jellyfin-клиент или Grafana-дашборд для мониторинга нагрузки) достаточно расширить docker-compose.yml и добавить соответствующие systemd-таймеры для резервных копий.
См. подробнее о подходе к «интеллектуальному» домашнему серверу в статье Что ещё полезного можно сделать со своим сервером — Журнал «Код».
Личный облачный хостинг Nextcloud с расширенной безопасностью
В домашней лаборатории Nextcloud часто становится «единой точкой входа» для файлов, календарей и контактов. Чтобы эта точка была предсказуемой и устойчива к внешним угрозам, я обычно делаю два шага: настраиваю HTTPS через Caddy с автоматическим сертификатом Let’s Encrypt и включаю двухфакторную аутентификацию + сканер вредоносных файлов ClamAV. Ниже – пошаговый набор команд и конфигов, проверенный на ноде «Винни-Пух» (Ubuntu 22.04 LTS).
Конфигурация HTTPS через Caddy с автоматическим сертификатом Let’s Encrypt
- Установить Caddy – в репозитории Ubuntu уже есть готовый пакет, но я предпочитаю официальные бинарники, чтобы сразу получить последнюю версию.
bash# скачиваем и ставим последнюю стабильную сборку
curl -1Lf https://github.com/caddyserver/caddy/releases/download/v2.8.4/caddy_2.8.4_linux_amd64.tar.gz \
-o caddy.tar.gz
sudo tar -xzf caddy.tar.gz -C /usr/local/bin caddy
sudo chmod +x /usr/local/bin/caddy
# создаём systemd-службу
cat <<'EOF' | sudo tee /etc/systemd/system/caddy.service
[Unit]
Description=Caddy web server
After=network.target
[Service]
User=root
Group=root
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWriteDirectories=/var/www /etc/caddy
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now caddy
- Создать Caddyfile – Caddy умеет получать сертификат Let’s Encrypt «из коробки». Важно, чтобы DNS-запись
nextcloud.example.comуже указывала на ваш публичный IP.
bashsudo mkdir -p /etc/caddy
cat <<'EOF' | sudo tee /etc/caddy/Caddyfile
nextcloud.example.com {
reverse_proxy 127.0.0.1:8080 # порт, где работает контейнер Nextcloud
encode gzip
file_server
# Принудительно включаем HSTS и безопасные заголовки
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "no-referrer-when-downgrade"
}
# Автоматический редирект HTTP→HTTPS
@http {
protocol http
}
redir @http https://{host}{uri} permanent
}
EOF
- Перезапустить Caddy
bashsudo systemctl reload caddy
После этого Caddy запросит сертификат у Let’s Encrypt, разместит его в /root/.caddy и будет автоматически обновлять каждые 90 дней. Проверить можно командой curl -I https://nextcloud.example.com, в заголовке Strict-Transport-Security должно быть значение из конфига.
Подробнее о возможностях Caddy см. в официальной документации — Getting Started with Caddy.
Включение двухфактории и сканера вредоносных файлов ClamAV
2.1 Двухфакторная аутентификация (2FA) в Nextcloud
Nextcloud уже поставляется с поддержкой OTP-токенов (Google Authenticator, Authy и т.п.). Чтобы включить её глобально:
- Зайти в админ-панель → «Безопасность» → «Двухфакторная аутентификация».
- Отметить «Включить двухфакторную аутентификацию» и выбрать провайдер «TOTP».
- Сохранить – каждый пользователь получит QR-код при первом входе.
Для автоматизации можно задать параметр в config.php:
php<?php
$CONFIG = array (
'twofactor_enforced' => true, // обязательная 2FA для всех пользователей
'twofactor_enforced_groups' => array('admin', 'family'), // если нужно только для групп
);
Перезапуск PHP-фпм (если используется) гарантирует применение:
bashsudo systemctl reload php8.2-fpm
2.2 ClamAV + Nextcloud — сканирование загружаемых файлов
- Установить ClamAV и запустить демона.
bashsudo apt update
sudo apt install -y clamav clamav-daemon
sudo systemctl enable --now clamav-daemon
- Обновить сигнатуры (можно добавить в cron, но
clamav-freshclamуже ставит таймер).
bashsudo freshclam
-
Подключить приложение «Antivirus for files» в Nextcloud (Marketplace → «Antivirus»). После установки в настройках указываем:
- Backend —
ClamAV daemon - Host —
127.0.0.1 - Port —
3310 - Maximum file size —
100 MB(по желанию)
- Backend —
-
Проверить работу – загрузите тестовый файл
eicar.com(стандартный тест-вирус). Если всё настроено, Nextcloud откажет в загрузке и в журнале появится запись вида:
[2026-06-10 12:34:56.789] security WARNING: File eicar.com blocked by ClamAV (EICAR-Test-File)
2.3 Автоматический сканинг существующего хранилища
Для однократного сканирования всех уже загруженных файлов удобно воспользоваться скриптом:
bash#!/usr/bin/env bash
NEXTCLOUD_DATA="/var/www/nextcloud/data"
find "$NEXTCLOUD_DATA" -type f -print0 | while IFS= read -r -d '' file; do
if clamscan -i "$file" | grep -q FOUND; then
echo "Infected: $file"
# перемещаем в карантин
mv "$file" "${file}.quarantine"
fi
done
Запускаем от пользователя www-data (или того, под которым работает PHP), чтобы иметь доступ к файлам:
bashsudo -u www-data bash scan_nextcloud.sh
2.4 Мониторинг статуса ClamAV
Для предсказуемой работы я добавляю простой systemd-таймер, который проверяет, что clamav-daemon отвечает:
ini# /etc/systemd/system/clamav-health.service
[Unit]
Description=Check ClamAV daemon health
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/clamdscan --ping
ExecStartPost=/usr/bin/logger -t clamav-health "Daemon OK"
ExecStartPost=-/usr/bin/logger -t clamav-health "Daemon NOT responding"
# /etc/systemd/system/clamav-health.timer
[Unit]
Description=Run ClamAV health check every 5 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true
[Install]
WantedBy=timers.target
bashsudo systemctl enable --now clamav-health.timer
Теперь в journalctl -u clamav-health будет видно, когда демон недоступен, и можно настроить алерт в Grafana/Prometheus.
Итого: Caddy берёт на себя всё, что касается TLS, а встроенный в Nextcloud модуль 2FA + отдельный сканер ClamAV закрывают два главных вектора атак — краже учётных данных и загрузке вредоносного кода. Такой набор конфигураций легко масштабировать на остальные ноды кластера («Пятачок», «Сова»), а благодаря systemd-юнитам всё остаётся предсказуемым и документированным.
Система мониторинга и алертинга для домашней инфраструктуры
Prometheus + node_exporter + blackbox_exporter в k3s
Для домашней инфраструктуры я предпочитаю разворачивать стек мониторинга в небольшом кластере k3s. Это даёт предсказуемость — все компоненты управляются единой системой — и лёгкую масштабируемость, если добавятся новые ноды (например, «Винни-Пух», «Пятачок»).
1. Подготовка кластера
bash# Установим k3s (скрипт от Rancher)
curl -sfL https://get.k3s.io | sh -
# Проверим, что кластер работает
kubectl get nodes
Совет: если планируете использовать несколько нод, добавьте их в файл /etc/rancher/k3s/k3s.yaml через параметр
--server/--agent.
2. Деплой Prometheus и экспортеров через Helm
bash# Добавим репозиторий prometheus-community
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Установим chart с минимальными настройками
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace \
--set prometheus.prometheusSpec.serviceMonitorSelectorNilUsesHelmValues=false
Chart уже включает node_exporter. Для blackbox_exporter добавим отдельный ServiceMonitor.
yaml# blackbox-exporter.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: blackbox-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app.kubernetes.io/name: blackbox-exporter
endpoints:
- port: http
path: /probe
params:
module: [http_2xx]
relabelings:
- sourceLabels: [__address__]
targetLabel: __param_target
- sourceLabels: [__param_target]
targetLabel: instance
- targetLabel: __address__
replacement: blackbox-exporter.monitoring.svc.cluster.local:9115
bash# Применяем манифест
kubectl apply -f blackbox-exporter.yaml
3. Пример правила проверки доступности домашнего Plex-сервера
yaml# plex-availability.yaml
apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
name: plex-http
namespace: monitoring
spec:
jobName: "blackbox"
metricsPath: /probe
params:
module: [http_2xx]
staticConfig:
static:
- targets:
- "plex.home.local:32400"
bashkubectl apply -f plex-availability.yaml
После применения правило появится в Prometheus, и вы сможете построить график probe_success{instance="plex.home.local:32400"}.
4. Хранение метрик
Для небольших домашних кластеров хватает локального persistentVolume. В Helm-чарте укажите:
yamlprometheus:
prometheusSpec:
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: local-path
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
Grafana-дашборды
Grafana уже включена в kube-prometheus-stack. Доступ получаем через kubectl port-forward:
bashkubectl -n monitoring port-forward svc/grafana 3000:80
# логин admin / пароль admin (после первого входа сразу смените)
1. Импорт готового дашборда
В официальном репозитории Grafana есть набор «Home-Lab Overview» (ID = 1860). Чтобы импортировать:
- Откройте «Create → Import».
- Введите
1860и нажмите «Load». - Укажите datasource
Prometheusи сохраните.
2. Настройка собственного дашборда для Plex
json{
"title": "Plex Availability",
"panels": [
{
"type": "graph",
"title": "HTTP-probe success",
"targets": [
{
"expr": "probe_success{instance=\"plex.home.local:32400\"}",
"legendFormat": "{{instance}}"
}
],
"yaxes": [{ "format": "percent", "min": "0", "max": "1" }]
}
]
}
Сохраните JSON в файл plex-dashboard.json и импортируйте через UI.
Alertmanager + Telegram
Alertmanager отвечает за доставку уведомлений. В k3s он уже развернут в составе kube-prometheus-stack. Нужно лишь добавить канал Telegram.
1. Получаем Bot-токен
Создайте бота в @BotFather, запомните токен, затем запросите chat_id у @userinfobot.
2. Секрет с токеном
bashkubectl -n monitoring create secret generic telegram-secret \
--from-literal=bot-token='123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11'
3. Конфигурация Alertmanager
yaml# alertmanager-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: alertmanager-config
namespace: monitoring
data:
alertmanager.yml: |
global:
resolve_timeout: 5m
route:
receiver: telegram
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receivers:
- name: telegram
webhook_configs:
- url: 'https://api.telegram.org/bot{{ .Values.telegram.botToken }}/sendMessage'
send_resolved: true
http_config:
bearer_token_file: /etc/alertmanager/secrets/telegram-token
relabel_configs:
- source_labels: [__alert_name__]
target_label: text
replacement: '⚠️ {{ .AlertName }}'
- source_labels: [__alert_message__]
target_label: chat_id
replacement: '123456789' # ваш chat_id
bash# Применяем ConfigMap
kubectl apply -f alertmanager-config.yaml
# Перезапускаем Alertmanager, чтобы он подхватил изменения
kubectl -n monitoring rollout restart deployment/alertmanager-monitoring-alertmanager
4. Пример правила алерта для Plex
yaml# plex-alert.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: plex-alert
namespace: monitoring
spec:
groups:
- name: plex.rules
rules:
- alert: PlexDown
expr: probe_success{instance="plex.home.local:32400"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Plex недоступен"
description: "HTTP-пробе к Plex не возвращает 2xx более 2 минут"
bashkubectl apply -f plex-alert.yaml
После того как правило сработает, Alertmanager отправит сообщение в Telegram:
⚠️ PlexDown
Plex недоступен
HTTP-пробе к Plex не возвращает 2xx более 2 минут
Итоги
- Prometheus + node_exporter + blackbox_exporter в k3s дают полную видимость как системных метрик, так и доступности сервисов.
- Grafana превращает сырые метрики в удобные дашборды; импорт готовых шаблонов ускоряет старт.
- Alertmanager с Telegram-каналом обеспечивает мгновенную реакцию на сбои, а хранение правил в виде
PrometheusRuleделает конфигурацию декларативной и версионируемой.
Эта комбинация проверена в моей домашней лаборатории — сервер «Сова» уже более года собирает метрики без простоев, а уведомления в Telegram позволяют реагировать в реальном времени, не теряя предсказуемости инфраструктуры.
Автоматический бэкап семейных фото и видео на ZFS-пул
Автоматический бэкап семейных фото и видео на ZFS-пул
Семейные медиа-файлы часто становятся самым ценным «активом» домашней лаборатории. Их потеря — это не просто неудобство, а реальная эмоциональная нагрузка. Ниже описываю проверенный процесс: снимки ZFS-пула реплицируются в удалённое S3-совместимое хранилище, а systemd-таймер проверяет целостность и отсылает отчёт на почту.
Настройка репликации sнимка между TrueNAS и удалённым S3-совместимым хранилищем
1. Подготовка ZFS-пула
На TrueNAS (у меня сервер «Винни-Пух») пул называется tank. В нём хранится каталог photos — точка монтирования /mnt/tank/photos. Чтобы репликация была предсказуемой, включаем автоматическое создание снимков каждый день:
bash# /etc/cron.d/zfs-snapshots
0 3 * * * root zfs snapshot -r tank/photos@$(date +\%Y-\%m-\%d)
Почему cron, а не systemd?
Снимок — это одноразовое действие, а cron гарантирует, что он будет выполнен даже если система будет перезагружена в момент запуска таймера.
2. Установка s3cmd
Для отправки данных в S3-совместимый бакет (я использую Backblaze B2) ставим лёгкий клиент s3cmd:
bashapt-get update && apt-get install -y s3cmd
s3cmd --configure
# В интерактивном режиме вводим:
# Access Key, Secret Key, Endpoint (https://s3.us-west-002.backblazeb2.com), …
3. Скрипт репликации
Создаём отдельный скрипт, который будет искать новые снимки, экспортировать их в zfs send, а затем загружать в бакет. Файл сохраняем как /usr/local/bin/zfs-s3-replicate.sh и делаем исполняемым.
bash#!/usr/bin/env bash
set -euo pipefail
POOL="tank"
DATASET="photos"
REMOTE_BUCKET="s3://myfamily-backup/photos"
LOGFILE="/var/log/zfs-s3-replicate.log"
# Получаем список уже отправленных снимков
sent=$(s3cmd ls "${REMOTE_BUCKET}/" | awk '{print $4}' | sed 's#.*/##' | sort)
# Находим новые локальные снимки
for snap in $(zfs list -t snapshot -o name -H "${POOL}/${DATASET}" | sort); do
name=$(basename "${snap}")
if [[ " ${sent[@]} " =~ " ${name} " ]]; then
continue
fi
echo "$(date +%F\ %T) – отправляю ${snap}" >> "${LOGFILE}"
zfs send -R "${snap}" | s3cmd put - "${REMOTE_BUCKET}/${name}.zfs"
echo "$(date +%F\ %T) – завершено ${snap}" >> "${LOGFILE}"
done
Совет: храните скрипт в отдельном репозитории Git, чтобы иметь историю изменений и возможность отката.
4. Systemd-служба и таймер
Чтобы запускать репликацию каждый вечер, описываем unit-файл:
ini# /etc/systemd/system/zfs-s3-replicate.service
[Unit]
Description=Репликация ZFS-сайпшотов в S3
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/zfs-s3-replicate.sh
ini# /etc/systemd/system/zfs-s3-replicate.timer
[Unit]
Description=Таймер для zfs-s3-replicate.service
[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true
[Install]
WantedBy=timers.target
bashsystemctl daemon-reload
systemctl enable --now zfs-s3-replicate.timer
Таймер гарантирует, что даже после отключения питания репликация возобновится в следующий запланированный слот.
5. Проверка
После первого запуска убедитесь, что в бакете появились файлы вида 2024-11-02.zfs. Можно быстро проверить целостность, скачав один из них и сравнив контрольные суммы:
bashs3cmd get s3://myfamily-backup/photos/2024-11-02.zfs /tmp/ zfs receive -Fv tank/photos < /tmp/2024-11-02.zfs
Скрипт systemd-таймера, который проверяет целостность и отправляет отчёт
Наличие репликации — это только половина дела. Нужно регулярно проверять, что полученные файлы не повреждены, и уведомлять о статусе. Делается это в два шага: checksum-проверка и отправка письма через mailx.
1. Генерация и хранение контрольных сумм
При каждой репликации скрипт выше уже пишет лог. Добавим в него запись SHA-256 хеша:
bash# Внутри цикла после отправки
hash=$(zfs send -R "${snap}" | sha256sum | awk '{print $1}')
echo "hash=${hash}" >> "${LOGFILE}"
Для удобства сохраняем хеши в отдельный файл на TrueNAS:
bash# После завершения репликации
echo "${name} ${hash}" >> /var/lib/zfs-s3-replicate/hashes.db
2. Проверочный скрипт
bash#!/usr/bin/env bash
set -euo pipefail
REMOTE_BUCKET="s3://myfamily-backup/photos"
HASH_DB="/var/lib/zfs-s3-replicate/hashes.db"
TMPDIR=$(mktemp -d)
REPORT="/tmp/zfs-backup-report.txt"
MAILTO="alex@example.com"
> "${REPORT}"
while read -r name hash; do
echo "Проверяю ${name}…" >> "${REPORT}"
s3cmd get "${REMOTE_BUCKET}/${name}.zfs" "${TMPDIR}/${name}.zfs" >/dev/null 2>&1
calc=$(sha256sum "${TMPDIR}/${name}.zfs" | awk '{print $1}')
if [[ "${calc}" == "${hash}" ]]; then
echo " OK (hash=${calc})" >> "${REPORT}"
else
echo " ❗ Ошибка: ожидается ${hash}, получено ${calc}" >> "${REPORT}"
fi
rm -f "${TMPDIR}/${name}.zfs"
done < "${HASH_DB}"
# Отправляем отчёт
if command -v mailx >/dev/null; then
mailx -s "Отчёт о бэкапе ZFS ($(date +%F))" "${MAILTO}" < "${REPORT}"
else
echo "mailx не установлен, отчёт сохранён в ${REPORT}"
fi
rm -rf "${TMPDIR}"
Сохраняем как /usr/local/bin/zfs-s3-verify.sh и делаем исполняемым.
3. Таймер для проверки
ini# /etc/systemd/system/zfs-s3-verify.service
[Unit]
Description=Проверка целостности ZFS-сайпшотов в S3
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/zfs-s3-verify.sh
ini# /etc/systemd/system/zfs-s3-verify.timer
[Unit]
Description=Таймер для zfs-s3-verify.service
[Timer]
OnCalendar=*-*-* 06:00:00
Persistent=true
[Install]
WantedBy=timers.target
bashsystemctl daemon-reload
systemctl enable --now zfs-s3-verify.timer
4. Что проверять в отчёте
- отсутствие строк
❗ Ошибка— полный успех; - наличие новых записей в
hashes.db— это значит, что репликация прошла, но проверка ещё не выполнена; - время выполнения — если процесс занимает более 30 минут, стоит увеличить интервал между репликациями.
Итоги
- Репликация снимков в S3 обеспечивает географическую изоляцию и экономит место на локальном пуле.
- Systemd-таймеры гарантируют предсказуемый запуск независимо от состояния системы.
- Хеш-контроль и автоматический e-mail-отчёт позволяют быстро реагировать на любые сбои, а журнал
zfs-s3-replicate.logслужит единой точкой аудита.
Эти небольшие, но надёжные кусочки инфраструктуры уже несколько лет защищают наши семейные фото от «провалов» и дают уверенность в том, что ни один кадр не исчезнет без следа.
AI-ассистент для домашней автоматизации и контент-генерации
AI-ассистент для домашней автоматизации и контент-генерации
В этом разделе я покажу, как превратить домашний сервер в «мозг» вашего умного дома и одновременно в генератор текста/кода. Всё разворачивается в контейнерах, управляется systemd и интегрируется с Home Assistant через webhook.
Развёртывание локального LLM (llama.cpp) в контейнере
Почему именно llama.cpp – лёгкая сборка, не требует CUDA, а значит работает даже на старой ноде «Сова» (Intel Xeon E5-2670, 64 ГБ RAM). При желании можно подключить GPU-пасс-тру, но для большинства бытовых задач (генерация ответов, коротких статей, автокомплит) 4 ГБ VRAM достаточно.
1. Подготовка образа
Создаём Dockerfile в каталоге ~/llama:
Dockerfile# syntax=docker/dockerfile:1.4 FROM debian:bookworm-slim AS builder # Утилиты RUN apt-get update && apt-get install -y --no-install-recommends \ git ca-certificates build-essential cmake \ && rm -rf /var/lib/apt/lists/* # Скачиваем исходники WORKDIR /src RUN git clone --depth 1 https://github.com/ggerganov/llama.cpp.git . RUN make -j$(nproc) # Финальный слой – только исполняемый файл FROM debian:bookworm-slim COPY --from=builder /src/llama.cpp /usr/local/bin/llama.cpp RUN apt-get update && apt-get install -y --no-install-recommends \ libstdc++6 ca-certificates \ && rm -rf /var/lib/apt/lists/* # Порт для HTTP-интерфейса EXPOSE 8080 ENTRYPOINT ["/usr/local/bin/llama.cpp"] CMD ["-m", "/models/ggml-model-q4_0.bin", "-p", "You are a helpful assistant.", "-t", "4", "-ngl", "0", "-fa", "0", "-c", "2048", "-n", "128", "-b", "256", "-a", "0.7", "-r", "0.0", "-s", "0", "-temp", "0.8", "-ctx", "2048", "--port", "8080"]
Примечание. Путь
/models/ggml-model-q4_0.bin– это место, куда мы позже положим модель. Форматq4_0экономит RAM в 4-5 раз.
2. Скачивание модели
bashmkdir -p /opt/llama/models
cd /opt/llama/models
# Пример модели LLaMA-2-7B-q4_0 (GPL-compatible)
wget -O ggml-model-q4_0.bin https://huggingface.co/TheBloke/Llama-2-7B-GGML/resolve/main/llama-2-7b.Q4_0.ggmlv3.bin
3. Systemd-юнит
ini# /etc/systemd/system/llama.service
[Unit]
Description=Local LLM (llama.cpp) container
After=network-online.target
Wants=network-online.target
[Service]
Restart=on-failure
ExecStartPre=/usr/bin/docker pull alpine:latest # гарантируем наличие базового образа
ExecStart=/usr/bin/docker run --rm \
--name llama \
-p 8080:8080 \
-v /opt/llama/models:/models:ro \
-v /opt/llama/data:/data \
--cpus=4 \
--memory=8g \
ghcr.io/llama-cpp/llama:latest
ExecStop=/usr/bin/docker stop llama
[Install]
WantedBy=multi-user.target
bashsystemctl daemon-reload
systemctl enable --now llama.service
Проверьте, что API отвечает:
bashcurl -X POST http://localhost:8080/completions \
-H "Content-Type: application/json" \
-d '{"prompt":"Привет, расскажи про ZFS"}'
Ответ придёт в JSON-формате, готовый к дальнейшей обработке.
Интеграция с Home Assistant через webhook
Home Assistant (HA) уже у меня работает в контейнере homeassistant на ноде «Винни-Пух». Мы будем использовать простой webhook, который отправляет запрос к LLM и получает ответ, а затем передаёт его в автоматизацию HA.
1. Создаём webhook-скрипт
bashmkdir -p /opt/ha-webhook
cat > /opt/ha-webhook/llama_webhook.py <<'PY'
#!/usr/bin/env python3
import os, sys, json, requests
from flask import Flask, request, abort
app = Flask(__name__)
LLAMA_URL = os.getenv('LLAMA_URL', 'http://localhost:8080/completions')
HEADERS = {'Content-Type': 'application/json'}
@app.route('/api/llama', methods=['POST'])
def handle():
data = request.get_json()
if not data or 'prompt' not in data:
abort(400, 'Missing prompt')
payload = {
"prompt": data['prompt'],
"max_tokens": 150,
"temperature": 0.8,
"stop": ["\n"]
}
resp = requests.post(LLAMA_URL, headers=HEADERS, json=payload, timeout=10)
resp.raise_for_status()
result = resp.json()
return json.dumps({"response": result.get('content', '')})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5005)
PY
chmod +x /opt/ha-webhook/llama_webhook.py
2. Docker-композит для webhook-сервиса
yaml# /opt/ha-webhook/docker-compose.yml
version: "3.9"
services:
llama-webhook:
image: python:3.12-slim
container_name: llama-webhook
restart: unless-stopped
ports:
- "5005:5005"
volumes:
- ./llama_webhook.py:/app/llama_webhook.py:ro
working_dir: /app
command: ["python", "llama_webhook.py"]
environment:
- LLAMA_URL=http://llama:8080/completions
depends_on:
- llama
llama:
image: ghcr.io/llama-cpp/llama:latest
container_name: llama
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /opt/llama/models:/models:ro
command: ["-m", "/models/ggml-model-q4_0.bin", "-p", "You are a helpful assistant.", "-t", "4", "--port", "8080"]
Запускаем:
bashcd /opt/ha-webhook
docker compose up -d
3. Добавляем webhook в Home Assistant
В configuration.yaml (или через UI → Интеграции → Webhook) создаём REST Command:
yamlrest_command:
llama_prompt:
url: http://{{ states('sensor.host_ip') }}:5005/api/llama
method: post
payload: '{"prompt":"{{ prompt }}"}'
content_type: application/json
timeout: 10
4. Пример автоматизации: включить свет и включить музыку по голосовой команде
yamlautomation:
- alias: "AI-управление светом и музыкой"
trigger:
- platform: event
event_type: ios.action_fired
event_data:
actionName: "turn_on_party_mode"
action:
- service: rest_command.llama_prompt
data:
prompt: "Включи свет в гостиной и поставь плейлист «Вечеринка 2024»"
- wait_template: "{{ is_state('media_player.living_room', 'playing') }}"
timeout: '00:00:15'
- service: notify.mobile_app_alexey_phone
data:
message: "Свет включён, музыка играет"
Когда вы вызываете действие turn_on_party_mode (например, через Siri Shortcut), Home Assistant отправит запрос к нашему webhook, LLM сформирует естественный ответ, а HA выполнит команды, закодированные в ответе (можно добавить парсер в llama_webhook.py, который будет извлекать entity_id и service из текста).
Краткий чек-лист
| Шаг | Что делаем | Где проверяем |
|---|---|---|
| 1 | Скачиваем модель в /opt/llama/models | ls -lh /opt/llama/models |
| 2 | Запускаем llama.service через systemd | systemctl status llama |
| 3 | Тестируем LLM API | curl -X POST … |
| 4 | Поднимаем webhook-контейнер (docker compose up -d) | docker ps |
| 5 | Добавляем rest_command в HA | UI → Настройки → Автоматизации |
| 6 | Создаём простую автоматизацию | automation editor |
Почему так работает
- Предсказуемость – каждый компонент (LLM, webhook, HA) живёт в отдельном контейнере, управляется systemd или Docker-композит. При падении контейнера systemd автоматически перезапустит сервис.
- Разделение ответственности – LLM отвечает только за генерацию текста, webhook – за трансляцию в JSON, HA – за исполнение действий. Это упрощает отладку и масштабирование.
- Локальность – всё находится в вашей домашней сети, никаких облачных API-ключей, никаких внешних точек отказа.
Что дальше
- Подключить GPU-пасс-тру на ноде «Пятачок» (NVIDIA RTX 3060) – заменив
--gpus allвdocker-композит.yml. - Добавить кэширование запросов в Redis, чтобы ускорять повторяющиеся запросы («Как включить свет?», «Какая погода?`).
- Интегрировать с Grafana-Alertmanager, чтобы получать алерт, если LLM начинает «залипать» (latency > 5 сек).
Эти шаги позволят превратить ваш домашний сервер из «мелкой коробки» в надёжный, предсказуемый центр умного дома и креативного контент-генератора.