<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Заметки о домашнем сервере</title>
    <link>https://sparn.ru/</link>
    <description>sparn про Linux, Docker и всякий self-hosting. Пишу, чтобы не забыть.</description>
    <pubDate>Tue, 09 Jun 2026 19:46:27 +0000</pubDate>
    <item>
      <title>Переехал с Nextcloud на Immich для фоток</title>
      <link>https://sparn.ru/immich-foto</link>
      <description>&lt;![CDATA[Несколько лет я хранил фотки в Nextcloud — он у меня и так крутится на домашнем сервере, есть приложение Photos, синхронизация с телефона работает. Казалось бы, зачем что-то менять. А менять пришлось, потому что на коллекции в ~80 тысяч снимков всё это начало откровенно тормозить.&#xA;&#xA;Что бесило в Nextcloud&#xA;&#xA;Главная боль — листание галереи. Превьюшки генерируются лениво, и при заходе в папку с тысячами фоток интерфейс просто задумывался на несколько секунд. Поиск был никакой: только по имени файла и по дате. Найти «фотку с собакой на даче летом» — это руками листать.&#xA;&#xA;Распознавания лиц и объектов из коробки нет, надо ставить отдельное приложение Recognize, которое прожорливое и всё равно работало через раз. В общем, Nextcloud — отличный комбайн для файлов, но как фотохранилище он вторичен по дизайну.&#xA;&#xA;Почему Immich&#xA;&#xA;Immich заточен именно под фотки, и это чувствуется сразу. Что зацепило:&#xA;&#xA;Скорость. Галерея на десятки тысяч снимков скроллится плавно, превью отдаются мгновенно.&#xA;ML-поиск. Распознаёт лица (можно назвать человека — и вот все его фото), ищет по объектам. Я серьёзно набрал в поиске «cat» и получил всех котов из архива. Работает на CLIP-модели локально, ничего наружу не уходит.&#xA;Мобильное приложение. Реально хорошее. Автобэкап с телефона включил один раз и забыл — новые фотки сами уезжают на сервер.&#xA;Карта и таймлайн по EXIF — приятный бонус.&#xA;&#xA;Как поднял&#xA;&#xA;Стандартный compose-стек в /app/immich/, по официальному примеру. Ключевое — отдельный том под загрузки и Postgres с расширением для векторного поиска:&#xA;&#xA;services:&#xA;  immich-server:&#xA;    image: ghcr.io/immich-app/immich-server:release&#xA;    volumes:&#xA;      ${UPLOADLOCATION}:/usr/src/app/upload&#xA;      /etc/localtime:/etc/localtime:ro&#xA;    envfile: .env&#xA;    ports:&#xA;      2283:2283&#xA;    depends_on: [redis, database]&#xA;    restart: always&#xA;&#xA;  immich-machine-learning:&#xA;    image: ghcr.io/immich-app/immich-machine-learning:release&#xA;    volumes:&#xA;      model-cache:/cache&#xA;    restart: always&#xA;&#xA;ML-контейнер первый раз тянет модели — пара минут, и потом индексация всего архива идёт в фоне. На моём железе без GPU 80 тысяч фоток прожевались за ночь.&#xA;&#xA;Импорт старого архива сделал через immich-cli:&#xA;&#xA;immich upload --recursive /mnt/photos/archive&#xA;&#xA;Осадочек&#xA;&#xA;Immich официально предупреждает, что он ещё развивается и формат может меняться между релизами — поэтому я не выкинул старый архив и держу его отдельной холодной копией. Бэкаплю и базу Postgres, и папку upload. Но за полгода ни одного сюрприза не словил, обновления накатываются чисто.&#xA;&#xA;Nextcloud у меня остался — но теперь чисто под документы и файлопомойку, чем он, собственно, и хорош.]]&gt;</description>
      <content:encoded><![CDATA[<p>Несколько лет я хранил фотки в Nextcloud — он у меня и так крутится на домашнем сервере, есть приложение Photos, синхронизация с телефона работает. Казалось бы, зачем что-то менять. А менять пришлось, потому что на коллекции в ~80 тысяч снимков всё это начало откровенно тормозить.</p>

<h2 id="что-бесило-в-nextcloud">Что бесило в Nextcloud</h2>

<p>Главная боль — листание галереи. Превьюшки генерируются лениво, и при заходе в папку с тысячами фоток интерфейс просто задумывался на несколько секунд. Поиск был никакой: только по имени файла и по дате. Найти «фотку с собакой на даче летом» — это руками листать.</p>

<p>Распознавания лиц и объектов из коробки нет, надо ставить отдельное приложение Recognize, которое прожорливое и всё равно работало через раз. В общем, Nextcloud — отличный комбайн для файлов, но как фотохранилище он вторичен по дизайну.</p>

<h2 id="почему-immich">Почему Immich</h2>

<p>Immich заточен именно под фотки, и это чувствуется сразу. Что зацепило:</p>
<ul><li><strong>Скорость.</strong> Галерея на десятки тысяч снимков скроллится плавно, превью отдаются мгновенно.</li>
<li><strong>ML-поиск.</strong> Распознаёт лица (можно назвать человека — и вот все его фото), ищет по объектам. Я серьёзно набрал в поиске «cat» и получил всех котов из архива. Работает на CLIP-модели локально, ничего наружу не уходит.</li>
<li><strong>Мобильное приложение.</strong> Реально хорошее. Автобэкап с телефона включил один раз и забыл — новые фотки сами уезжают на сервер.</li>
<li><strong>Карта и таймлайн</strong> по EXIF — приятный бонус.</li></ul>

<h2 id="как-поднял">Как поднял</h2>

<p>Стандартный compose-стек в <code>/app/immich/</code>, по официальному примеру. Ключевое — отдельный том под загрузки и Postgres с расширением для векторного поиска:</p>

<pre><code class="language-yaml">services:
  immich-server:
    image: ghcr.io/immich-app/immich-server:release
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file: .env
    ports:
      - 2283:2283
    depends_on: [redis, database]
    restart: always

  immich-machine-learning:
    image: ghcr.io/immich-app/immich-machine-learning:release
    volumes:
      - model-cache:/cache
    restart: always
</code></pre>

<p>ML-контейнер первый раз тянет модели — пара минут, и потом индексация всего архива идёт в фоне. На моём железе без GPU 80 тысяч фоток прожевались за ночь.</p>

<p>Импорт старого архива сделал через <code>immich-cli</code>:</p>

<pre><code class="language-bash">immich upload --recursive /mnt/photos/archive
</code></pre>

<h2 id="осадочек">Осадочек</h2>

<p>Immich официально предупреждает, что он ещё развивается и формат может меняться между релизами — поэтому я <strong>не выкинул</strong> старый архив и держу его отдельной холодной копией. Бэкаплю и базу Postgres, и папку upload. Но за полгода ни одного сюрприза не словил, обновления накатываются чисто.</p>

<p>Nextcloud у меня остался — но теперь чисто под документы и файлопомойку, чем он, собственно, и хорош.</p>
]]></content:encoded>
      <guid>https://sparn.ru/immich-foto</guid>
      <pubDate>Tue, 09 Jun 2026 17:44:51 +0000</pubDate>
    </item>
    <item>
      <title>Год с домашним сервером: что бы я сделал иначе</title>
      <link>https://sparn.ru/god-s-domashnim-serverom</link>
      <description>&lt;![CDATA[Ровно год назад я собрал свой первый настоящий домашний сервер. До этого был старый ноут с пыхтящим вентилятором, на котором кое-как крутилась пара контейнеров, но это не считается. А тут — нормальный мини-сервер на i5-12400, который сейчас держит медиатеку, заметки, умный дом, файлохранилище и мониторинг. Пора подвести итоги: что зашло, а на каких граблях я станцевал и чего бы себе посоветовал год назад.&#xA;&#xA;Что бы сделал иначе&#xA;&#xA;Взял бы сразу больше RAM. Стартовал с 16 ГБ, думал «куда мне больше». К осени упёрся: Jellyfin при транскоде, Home Assistant, пара баз данных, мониторинг — и swap начал поскрипывать. Докупил до 64 ГБ, и сразу стало вольготно. RAM — это то, на чём в домашнем сервере экономить не стоит, она дешёвая, а заканчивается всегда внезапно.&#xA;&#xA;Сразу взял бы нормальный SSD под систему и контейнеры. Я зачем-то поставил систему на старый SATA-SSD «который валялся», а под данные — большой HDD. В итоге часть баз и кэшей жила на медленном диске, и я месяц гадал, почему интерфейсы подтормаживают. Поставил NVMe — всё залетало. Под систему и горячие данные — только NVMe, HDD оставить для медиа и архивов.&#xA;&#xA;Подписал бы кабели. Смешно, но это реально один из главных уроков. За год накопилось столько проводов — питание, два сетевых, USB-донгл, диски — что когда что-то надо переткнуть, я каждый раз играю в сапёра. Купил бы за копейки набор маркеров для кабелей в самом начале. Сейчас всё подписано, но прозрение пришло через боль.&#xA;&#xA;Никогда не держал бы прод-данные без бэкапа. Тут была почти трагедия. Семейные заметки и фотоархив какое-то время жили в единственном экземпляре на одном диске. Диск начал сыпать ошибками в SMART, я успел в последний момент. После этого настроил нормальные бэкапы: ежедневный снапшот важных данных на второй диск и еженедельная выгрузка в облако шифрованным архивом. Правило простое: если данные существуют в одном месте — их не существует.&#xA;&#xA;то, что надо было сделать в первый же день, а не в панике потом&#xA;restic -r /mnt/backup/restic backup /app /mnt/media/photos&#xA;restic -r /mnt/backup/restic forget --keep-daily 7 --keep-weekly 4 --prune&#xA;&#xA;Чему научился&#xA;&#xA;Что Docker Compose — это лучшее, что случилось с самохостингом. Весь мой сервер описан текстовыми файлами в /app/сервис/, и если железо завтра помрёт, я подниму всё на новом за вечер. Раньше я настраивал сервисы руками и не помнил через месяц, что и где менял.&#xA;&#xA;Что мониторинг и алерты надо ставить не «когда-нибудь потом», а сразу. Пока сервис не следит сам за собой, ты узнаёшь о проблемах от домашних, а это худший вид мониторинга.&#xA;&#xA;Что не надо гнаться за «правильным» и сложным решением, если простое закрывает задачу. Я пару раз закапывался в навороченные стеки, хотя хватило бы одного контейнера.&#xA;&#xA;Что реально пригодилось&#xA;&#xA;Из всего зоопарка ежедневно работают три вещи: медиатека (её любит вся семья), умный дом с датчиками протечки (уже дважды спас от потопа) и заметки. Остальное — приятный бонус и площадка для экспериментов.&#xA;&#xA;Год назад это был способ «поиграться с технологиями». Сейчас это незаметная инфраструктура дома, которой пользуются все, не зная, что там внутри. По-моему, это и есть признак, что всё получилось. Спасибо, что читаете, — впереди ещё много экспериментов.]]&gt;</description>
      <content:encoded><![CDATA[<p>Ровно год назад я собрал свой первый настоящий домашний сервер. До этого был старый ноут с пыхтящим вентилятором, на котором кое-как крутилась пара контейнеров, но это не считается. А тут — нормальный мини-сервер на i5-12400, который сейчас держит медиатеку, заметки, умный дом, файлохранилище и мониторинг. Пора подвести итоги: что зашло, а на каких граблях я станцевал и чего бы себе посоветовал год назад.</p>

<h2 id="что-бы-сделал-иначе">Что бы сделал иначе</h2>

<p><strong>Взял бы сразу больше RAM.</strong> Стартовал с 16 ГБ, думал «куда мне больше». К осени упёрся: Jellyfin при транскоде, Home Assistant, пара баз данных, мониторинг — и swap начал поскрипывать. Докупил до 64 ГБ, и сразу стало вольготно. RAM — это то, на чём в домашнем сервере экономить не стоит, она дешёвая, а заканчивается всегда внезапно.</p>

<p><strong>Сразу взял бы нормальный SSD под систему и контейнеры.</strong> Я зачем-то поставил систему на старый SATA-SSD «который валялся», а под данные — большой HDD. В итоге часть баз и кэшей жила на медленном диске, и я месяц гадал, почему интерфейсы подтормаживают. Поставил NVMe — всё залетало. Под систему и горячие данные — только NVMe, HDD оставить для медиа и архивов.</p>

<p><strong>Подписал бы кабели.</strong> Смешно, но это реально один из главных уроков. За год накопилось столько проводов — питание, два сетевых, USB-донгл, диски — что когда что-то надо переткнуть, я каждый раз играю в сапёра. Купил бы за копейки набор маркеров для кабелей в самом начале. Сейчас всё подписано, но прозрение пришло через боль.</p>

<p><strong>Никогда не держал бы прод-данные без бэкапа.</strong> Тут была почти трагедия. Семейные заметки и фотоархив какое-то время жили в единственном экземпляре на одном диске. Диск начал сыпать ошибками в SMART, я успел в последний момент. После этого настроил нормальные бэкапы: ежедневный снапшот важных данных на второй диск и еженедельная выгрузка в облако шифрованным архивом. Правило простое: если данные существуют в одном месте — их не существует.</p>

<pre><code class="language-bash"># то, что надо было сделать в первый же день, а не в панике потом
restic -r /mnt/backup/restic backup /app /mnt/media/photos
restic -r /mnt/backup/restic forget --keep-daily 7 --keep-weekly 4 --prune
</code></pre>

<h2 id="чему-научился">Чему научился</h2>

<p>Что Docker Compose — это лучшее, что случилось с самохостингом. Весь мой сервер описан текстовыми файлами в <code>/app/&lt;сервис&gt;/</code>, и если железо завтра помрёт, я подниму всё на новом за вечер. Раньше я настраивал сервисы руками и не помнил через месяц, что и где менял.</p>

<p>Что мониторинг и алерты надо ставить не «когда-нибудь потом», а сразу. Пока сервис не следит сам за собой, ты узнаёшь о проблемах от домашних, а это худший вид мониторинга.</p>

<p>Что не надо гнаться за «правильным» и сложным решением, если простое закрывает задачу. Я пару раз закапывался в навороченные стеки, хотя хватило бы одного контейнера.</p>

<h2 id="что-реально-пригодилось">Что реально пригодилось</h2>

<p>Из всего зоопарка ежедневно работают три вещи: медиатека (её любит вся семья), умный дом с датчиками протечки (уже дважды спас от потопа) и заметки. Остальное — приятный бонус и площадка для экспериментов.</p>

<p>Год назад это был способ «поиграться с технологиями». Сейчас это незаметная инфраструктура дома, которой пользуются все, не зная, что там внутри. По-моему, это и есть признак, что всё получилось. Спасибо, что читаете, — впереди ещё много экспериментов.</p>
]]></content:encoded>
      <guid>https://sparn.ru/god-s-domashnim-serverom</guid>
      <pubDate>Sun, 26 Apr 2026 12:50:00 +0000</pubDate>
    </item>
    <item>
      <title>Мониторинг по-домашнему: Uptime Kuma, чуть-чуть Grafana и алерты в Telegram</title>
      <link>https://sparn.ru/monitoring-uptime-kuma</link>
      <description>&lt;![CDATA[Какое-то время мой подход к мониторингу сервера был простой: что-то отвалилось — узнаю, когда сам захочу зайти и обнаружу, что не работает. Обычно в самый неудобный момент. Надоело. Хотелось, чтобы сервер сам стучался мне в телефон, когда какой-нибудь сервис лёг или диск начал забиваться. Без энтерпрайз-эзотерики на пол-стойки — у меня дома один сервер, а не дата-центр.&#xA;&#xA;В итоге собрал связку из двух частей: Uptime Kuma следит, что сервисы вообще живые, а Grafana с nodeexporter показывает, как себя чувствует железо. Оба алертят в Telegram.&#xA;&#xA;Uptime Kuma&#xA;&#xA;Это самая приятная штука для домашнего мониторинга, что я видел. Красивый дашборд со статусом всех сервисов, история аптайма, и настраивается мышкой за пять минут. Поднимаю в /app/uptime-kuma/:&#xA;&#xA;services:&#xA;  uptime-kuma:&#xA;    image: louislam/uptime-kuma:1&#xA;    containername: uptime-kuma&#xA;    volumes:&#xA;      /app/uptime-kuma/data:/app/data&#xA;    ports:&#xA;      &#34;3001:3001&#34;&#xA;    restart: unless-stopped&#xA;&#xA;Дальше через веб-морду добавляю мониторы: HTTP-проверку на каждую веб-морду (медиатека, заметки, реверс-прокси), TCP-пинг на MQTT-брокер, обычный ping на роутер и на пару внешних адресов, чтобы понимать, дома проблема или у провайдера. Каждому монитору цепляю Telegram-нотификацию.&#xA;&#xA;Бот делается тривиально: пишем @BotFather, командой /newbot получаем токен, потом узнаём свой chat id (я просто написал боту и дёрнул https://api.telegram.org/botТОКЕН/getUpdates). Эти два значения вставляем в настройки нотификации Uptime Kuma — и всё. Когда сервис не отвечает 60 секунд, в телефон падает сообщение, когда поднялся — приходит «recovered».&#xA;&#xA;Grafana и железо&#xA;&#xA;Uptime Kuma знает только «жив/не жив». А мне ещё интересно, не перегревается ли процессор летом и не кончается ли место на диске. Для этого — nodeexporter, который снимает метрики с хоста, Prometheus, который их складывает, и Grafana, которая рисует графики.&#xA;&#xA;  nodeexporter:&#xA;    image: prom/node-exporter:latest&#xA;    containername: nodeexporter&#xA;    command:&#xA;      &#39;--path.rootfs=/host&#39;&#xA;    pid: host&#xA;    volumes:&#xA;      &#39;/:/host:ro,rslave&#39;&#xA;    ports:&#xA;      &#34;9100:9100&#34;&#xA;    restart: unless-stopped&#xA;&#xA;В Grafana импортирую готовый дашборд Node Exporter Full (id 1860) — и сразу получаю красивые графики CPU, RAM, дисков, сети и температуры. Особенно люблю панель с температурой: летом сразу видно, когда пора чистить радиатор от пыли.&#xA;&#xA;На критичные метрики навесил алерты прямо в Grafana — например, диск заполнен больше 85% или температура CPU выше 80 градусов держится пять минут. Алерты тоже уходят в тот же Telegram-бот, через тот же contact point. Один бот на всё, чтобы не плодить чаты.&#xA;&#xA;Пара выводов&#xA;&#xA;Не надо тащить полноценный Prometheus+Grafana, если хочется просто «упал/поднялся» — Uptime Kuma в одиночку закрывает 80% потребностей и ставится за минуты.&#xA;Алерты обязательно надо настраивать «с задержкой» (for: 5m и аналоги в Kuma), иначе при моргнувшем интернете телефон превращается в пулемёт уведомлений в три часа ночи. Проверено.&#xA;Самый ценный монитор — это ping на внешний адрес. Половина «падений» сервисов на деле оказывалась морганием провайдера, и теперь я это сразу вижу.&#xA;&#xA;Теперь сервер сам докладывает о проблемах, и я узнаю о них до того, как о них узнают домашние. Спокойствие за пару вечеров настройки.]]&gt;</description>
      <content:encoded><![CDATA[<p>Какое-то время мой подход к мониторингу сервера был простой: что-то отвалилось — узнаю, когда сам захочу зайти и обнаружу, что не работает. Обычно в самый неудобный момент. Надоело. Хотелось, чтобы сервер сам стучался мне в телефон, когда какой-нибудь сервис лёг или диск начал забиваться. Без энтерпрайз-эзотерики на пол-стойки — у меня дома один сервер, а не дата-центр.</p>

<p>В итоге собрал связку из двух частей: Uptime Kuma следит, что сервисы вообще живые, а Grafana с node_exporter показывает, как себя чувствует железо. Оба алертят в Telegram.</p>

<h2 id="uptime-kuma">Uptime Kuma</h2>

<p>Это самая приятная штука для домашнего мониторинга, что я видел. Красивый дашборд со статусом всех сервисов, история аптайма, и настраивается мышкой за пять минут. Поднимаю в <code>/app/uptime-kuma/</code>:</p>

<pre><code class="language-yaml">services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    volumes:
      - /app/uptime-kuma/data:/app/data
    ports:
      - &#34;3001:3001&#34;
    restart: unless-stopped
</code></pre>

<p>Дальше через веб-морду добавляю мониторы: HTTP-проверку на каждую веб-морду (медиатека, заметки, реверс-прокси), TCP-пинг на MQTT-брокер, обычный ping на роутер и на пару внешних адресов, чтобы понимать, дома проблема или у провайдера. Каждому монитору цепляю Telegram-нотификацию.</p>

<p>Бот делается тривиально: пишем <code>@BotFather</code>, командой <code>/newbot</code> получаем токен, потом узнаём свой chat id (я просто написал боту и дёрнул <code>https://api.telegram.org/bot&lt;ТОКЕН&gt;/getUpdates</code>). Эти два значения вставляем в настройки нотификации Uptime Kuma — и всё. Когда сервис не отвечает 60 секунд, в телефон падает сообщение, когда поднялся — приходит «recovered».</p>

<h2 id="grafana-и-железо">Grafana и железо</h2>

<p>Uptime Kuma знает только «жив/не жив». А мне ещё интересно, не перегревается ли процессор летом и не кончается ли место на диске. Для этого — <code>node_exporter</code>, который снимает метрики с хоста, Prometheus, который их складывает, и Grafana, которая рисует графики.</p>

<pre><code class="language-yaml">  node_exporter:
    image: prom/node-exporter:latest
    container_name: node_exporter
    command:
      - &#39;--path.rootfs=/host&#39;
    pid: host
    volumes:
      - &#39;/:/host:ro,rslave&#39;
    ports:
      - &#34;9100:9100&#34;
    restart: unless-stopped
</code></pre>

<p>В Grafana импортирую готовый дашборд Node Exporter Full (id 1860) — и сразу получаю красивые графики CPU, RAM, дисков, сети и температуры. Особенно люблю панель с температурой: летом сразу видно, когда пора чистить радиатор от пыли.</p>

<p>На критичные метрики навесил алерты прямо в Grafana — например, диск заполнен больше 85% или температура CPU выше 80 градусов держится пять минут. Алерты тоже уходят в тот же Telegram-бот, через тот же contact point. Один бот на всё, чтобы не плодить чаты.</p>

<h2 id="пара-выводов">Пара выводов</h2>
<ul><li>Не надо тащить полноценный Prometheus+Grafana, если хочется просто «упал/поднялся» — Uptime Kuma в одиночку закрывает 80% потребностей и ставится за минуты.</li>
<li>Алерты обязательно надо настраивать «с задержкой» (<code>for: 5m</code> и аналоги в Kuma), иначе при моргнувшем интернете телефон превращается в пулемёт уведомлений в три часа ночи. Проверено.</li>
<li>Самый ценный монитор — это ping на внешний адрес. Половина «падений» сервисов на деле оказывалась морганием провайдера, и теперь я это сразу вижу.</li></ul>

<p>Теперь сервер сам докладывает о проблемах, и я узнаю о них до того, как о них узнают домашние. Спокойствие за пару вечеров настройки.</p>
]]></content:encoded>
      <guid>https://sparn.ru/monitoring-uptime-kuma</guid>
      <pubDate>Sun, 25 Jan 2026 18:15:00 +0000</pubDate>
    </item>
    <item>
      <title>Home Assistant и Zigbee-датчики: чтобы дом сам говорил, когда что-то не так</title>
      <link>https://sparn.ru/home-assistant-datchiki</link>
      <description>&lt;![CDATA[Всё началось с того, что у меня зимой в кладовке с трубами однажды чуть не прихватило воду — отопление барахлило, а я узнал об этом, только когда полез за консервами. Подумал: дом должен сам мне сообщать о таких вещах, а не я бегать с термометром. Так на сервере поселился Home Assistant.&#xA;&#xA;Home Assistant — это опенсорсный центр умного дома. Никаких облаков производителя, всё крутится локально, данные не утекают неизвестно куда, и работает даже если интернет отвалился.&#xA;&#xA;Запуск в Docker и Zigbee-донгл&#xA;&#xA;Я не стал плодить кучу облачных гаджетов от разных вендоров, а пошёл по пути Zigbee — это единый радиопротокол, к которому цепляется куча дешёвых датчиков от любых производителей. В сервер воткнут USB-донгл Sonoff ZBDongle-E (на чипе EFR32MG21), а разговаривает с ним Zigbee2MQTT.&#xA;&#xA;Связка такая: датчики → Zigbee2MQTT → MQTT-брокер (Mosquitto) → Home Assistant. Поднимаю всё одним compose в /app/homeassistant/:&#xA;&#xA;services:&#xA;  homeassistant:&#xA;    image: ghcr.io/home-assistant/home-assistant:stable&#xA;    containername: homeassistant&#xA;    networkmode: host&#xA;    volumes:&#xA;      /app/homeassistant/config:/config&#xA;      /etc/localtime:/etc/localtime:ro&#xA;    restart: unless-stopped&#xA;&#xA;  zigbee2mqtt:&#xA;    image: koenkk/zigbee2mqtt:latest&#xA;    containername: zigbee2mqtt&#xA;    volumes:&#xA;      /app/zigbee2mqtt/data:/app/data&#xA;    devices:&#xA;      /dev/serial/by-id/usb-ITeadSonoffZigbee3.0USBDonglePlusxxxx-if00:/dev/ttyACM0&#xA;    restart: unless-stopped&#xA;&#xA;Важный момент: устройство пробрасываю не как /dev/ttyACM0, а через стабильный путь /dev/serial/by-id/.... Иначе после перезагрузки донгл может переименоваться в ttyACM1, и Zigbee2MQTT его не найдёт. Точный путь смотрим командой ls -l /dev/serial/by-id/.&#xA;&#xA;Датчики&#xA;&#xA;По дому развесил недорогие Aqara: температура/влажность в комнатах, датчик протечки под мойкой и за стиралкой, пара датчиков открытия на дверях. Цепляются они через MQTT-интеграцию, после сопряжения сами появляются в Home Assistant как сущности вида sensor.temperaturakladovka или binarysensor.protechkamojka. Батарейки живут больше года, так что обслуживание нулевое.&#xA;&#xA;Автоматизации&#xA;&#xA;Вот ради чего всё затевалось. Две простые автоматизации закрыли мою главную боль. Первая — если в кладовке холодает ниже 8 градусов, прилетает уведомление на телефон:&#xA;&#xA;alias: &#34;Кладовка остывает&#34;&#xA;  trigger:&#xA;    platform: numericstate&#xA;      entityid: sensor.temperaturakladovka&#xA;      below: 8&#xA;      for:&#xA;        minutes: 10&#xA;  action:&#xA;    service: notify.mobileapptelefon&#xA;      data:&#xA;        title: &#34;Холодает в кладовке&#34;&#xA;        message: &#34;Температура {{ states(&#39;sensor.temperaturakladovka&#39;) }}°C — проверь отопление&#34;&#xA;&#xA;for: minutes: 10 тут не просто так — без него датчик может дёрнуться от сквозняка и завалить телефон ложными тревогами. А вот протечка — наоборот, реагируем мгновенно:&#xA;&#xA;alias: &#34;Протечка под мойкой&#34;&#xA;  trigger:&#xA;    platform: state&#xA;      entityid: binarysensor.protechkamojka&#xA;      to: &#34;on&#34;&#xA;  action:&#xA;    service: notify.mobileapptelefon&#xA;      data:&#xA;        title: &#34;⚠️ ПРОТЕЧКА&#34;&#xA;        message: &#34;Сработал датчик под мойкой!&#34;&#xA;        data:&#xA;          priority: high&#xA;&#xA;Итог&#xA;&#xA;С тех пор дом сам присматривает за собой. Зимой я спокоен за трубы, а пару раз датчик протечки реально срабатывал — один раз потёк шланг стиралки, и я узнал об этом сразу, а не по мокрому полу на следующий день. Следующий шаг — прикрутить реле, чтобы при протечке автоматически перекрывался кран на воду. Но это уже отдельная история с возможным затоплением во время отладки, так что подойду осторожно.]]&gt;</description>
      <content:encoded><![CDATA[<p>Всё началось с того, что у меня зимой в кладовке с трубами однажды чуть не прихватило воду — отопление барахлило, а я узнал об этом, только когда полез за консервами. Подумал: дом должен сам мне сообщать о таких вещах, а не я бегать с термометром. Так на сервере поселился Home Assistant.</p>

<p>Home Assistant — это опенсорсный центр умного дома. Никаких облаков производителя, всё крутится локально, данные не утекают неизвестно куда, и работает даже если интернет отвалился.</p>

<h2 id="запуск-в-docker-и-zigbee-донгл">Запуск в Docker и Zigbee-донгл</h2>

<p>Я не стал плодить кучу облачных гаджетов от разных вендоров, а пошёл по пути Zigbee — это единый радиопротокол, к которому цепляется куча дешёвых датчиков от любых производителей. В сервер воткнут USB-донгл Sonoff ZBDongle-E (на чипе EFR32MG21), а разговаривает с ним Zigbee2MQTT.</p>

<p>Связка такая: датчики → Zigbee2MQTT → MQTT-брокер (Mosquitto) → Home Assistant. Поднимаю всё одним compose в <code>/app/homeassistant/</code>:</p>

<pre><code class="language-yaml">services:
  homeassistant:
    image: ghcr.io/home-assistant/home-assistant:stable
    container_name: homeassistant
    network_mode: host
    volumes:
      - /app/homeassistant/config:/config
      - /etc/localtime:/etc/localtime:ro
    restart: unless-stopped

  zigbee2mqtt:
    image: koenkk/zigbee2mqtt:latest
    container_name: zigbee2mqtt
    volumes:
      - /app/zigbee2mqtt/data:/app/data
    devices:
      - /dev/serial/by-id/usb-ITead_Sonoff_Zigbee_3.0_USB_Dongle_Plus_xxxx-if00:/dev/ttyACM0
    restart: unless-stopped
</code></pre>

<p>Важный момент: устройство пробрасываю не как <code>/dev/ttyACM0</code>, а через стабильный путь <code>/dev/serial/by-id/...</code>. Иначе после перезагрузки донгл может переименоваться в <code>ttyACM1</code>, и Zigbee2MQTT его не найдёт. Точный путь смотрим командой <code>ls -l /dev/serial/by-id/</code>.</p>

<h2 id="датчики">Датчики</h2>

<p>По дому развесил недорогие Aqara: температура/влажность в комнатах, датчик протечки под мойкой и за стиралкой, пара датчиков открытия на дверях. Цепляются они через MQTT-интеграцию, после сопряжения сами появляются в Home Assistant как сущности вида <code>sensor.temperatura_kladovka</code> или <code>binary_sensor.protechka_mojka</code>. Батарейки живут больше года, так что обслуживание нулевое.</p>

<h2 id="автоматизации">Автоматизации</h2>

<p>Вот ради чего всё затевалось. Две простые автоматизации закрыли мою главную боль. Первая — если в кладовке холодает ниже 8 градусов, прилетает уведомление на телефон:</p>

<pre><code class="language-yaml">- alias: &#34;Кладовка остывает&#34;
  trigger:
    - platform: numeric_state
      entity_id: sensor.temperatura_kladovka
      below: 8
      for:
        minutes: 10
  action:
    - service: notify.mobile_app_telefon
      data:
        title: &#34;Холодает в кладовке&#34;
        message: &#34;Температура {{ states(&#39;sensor.temperatura_kladovka&#39;) }}°C — проверь отопление&#34;
</code></pre>

<p><code>for: minutes: 10</code> тут не просто так — без него датчик может дёрнуться от сквозняка и завалить телефон ложными тревогами. А вот протечка — наоборот, реагируем мгновенно:</p>

<pre><code class="language-yaml">- alias: &#34;Протечка под мойкой&#34;
  trigger:
    - platform: state
      entity_id: binary_sensor.protechka_mojka
      to: &#34;on&#34;
  action:
    - service: notify.mobile_app_telefon
      data:
        title: &#34;⚠️ ПРОТЕЧКА&#34;
        message: &#34;Сработал датчик под мойкой!&#34;
        data:
          priority: high
</code></pre>

<h2 id="итог">Итог</h2>

<p>С тех пор дом сам присматривает за собой. Зимой я спокоен за трубы, а пару раз датчик протечки реально срабатывал — один раз потёк шланг стиралки, и я узнал об этом сразу, а не по мокрому полу на следующий день. Следующий шаг — прикрутить реле, чтобы при протечке автоматически перекрывался кран на воду. Но это уже отдельная история с возможным затоплением во время отладки, так что подойду осторожно.</p>
]]></content:encoded>
      <guid>https://sparn.ru/home-assistant-datchiki</guid>
      <pubDate>Sun, 19 Oct 2025 17:22:00 +0000</pubDate>
    </item>
    <item>
      <title>Переехал с nginx на Caddy — и перестал руками возиться с сертификатами</title>
      <link>https://sparn.ru/caddy-vmesto-nginx</link>
      <description>&lt;![CDATA[У меня на сервере крутится с десяток сервисов: медиатека, заметки, файлопомойка, пара админок. Все они слушают свои порты, а наружу торчит один реверс-прокси, который разруливает, кого куда пускать по доменному имени. Долгое время этим занимался nginx. Работал, не жаловался — но обслуживать его конфиги и сертификаты надоело настолько, что я в один выходной взял и переехал на Caddy.&#xA;&#xA;Что бесило в nginx&#xA;&#xA;Сам по себе nginx прекрасен, вопросов нет. Но вокруг него я насобирал зоопарк:&#xA;&#xA;отдельный certbot в cron, который выпускает и продлевает сертификаты Let&#39;s Encrypt;&#xA;руками прописанные пути к fullchain.pem и privkey.pem в каждом server-блоке;&#xA;бойлерплейт на редирект с 80 на 443, на sslprotocols, на заголовки;&#xA;и классика — забыл перезагрузить nginx после продления серта, словил протухший сертификат, узнал об этом от жены, которая не смогла зайти на семейные заметки.&#xA;&#xA;Каждый новый сервис — это копипаст здоровенного server-блока и правка путей. К десятому разу начинаешь думать, что должен быть способ проще.&#xA;&#xA;Caddyfile, который заменил всё&#xA;&#xA;Способ есть, и он называется Caddy. Весь мой конфиг для двух сервисов выглядит так:&#xA;&#xA;films.example-home.lan {&#xA;    reverseproxy localhost:8096&#xA;}&#xA;&#xA;notes.example-home.lan {&#xA;    reverseproxy localhost:8080&#xA;}&#xA;&#xA;Всё. Это не урезанный пример — это реально рабочий минимум. Caddy сам:&#xA;&#xA;сходит в Let&#39;s Encrypt, выпустит сертификат на каждый домен;&#xA;будет молча его продлевать в фоне, без cron и без моего участия;&#xA;поднимет редирект с HTTP на HTTPS;&#xA;выставит вменяемые заголовки безопасности по умолчанию.&#xA;&#xA;Запускаю всё тем же compose, конфиг лежит в /app/caddy/:&#xA;&#xA;services:&#xA;  caddy:&#xA;    image: caddy:2&#xA;    containername: caddy&#xA;    ports:&#xA;      &#34;80:80&#34;&#xA;      &#34;443:443&#34;&#xA;      &#34;443:443/udp&#34;   # HTTP/3 (QUIC)&#xA;    volumes:&#xA;      /app/caddy/Caddyfile:/etc/caddy/Caddyfile:ro&#xA;      /app/caddy/data:/data&#xA;      /app/caddy/config:/config&#xA;    restart: unless-stopped&#xA;&#xA;Обратите внимание на 443:443/udp — это для HTTP/3. Caddy умеет его из коробки, достаточно пробросить UDP-порт, и современные браузеры сами договорятся о QUIC. На спидтестах внутри локалки разницы я особо не заметил, но при доступе снаружи через мобильный интернет страницы админок стали ощутимо живее открываться.&#xA;&#xA;Перезагрузка без даунтайма&#xA;&#xA;Добавил новый сервис — дописал три строчки в Caddyfile и сделал:&#xA;&#xA;docker exec caddy caddy reload --config /etc/caddy/Caddyfile&#xA;&#xA;reload подхватывает изменения на лету, без обрыва текущих соединений. Никакого nginx -t &amp;&amp; systemctl reload с замиранием сердца.&#xA;&#xA;Что в итоге&#xA;&#xA;/data я смонтировал в volume не просто так — там лежат выпущенные сертификаты и ACME-аккаунт. Если его потерять, Caddy перевыпустит сертификаты заново, но можно упереться в рейт-лимиты Let&#39;s Encrypt. Так что эта папка — то, что обязательно попадает в бэкап.&#xA;&#xA;Конфиг ужался раз в десять, certbot из крона выпилил, про протухшие сертификаты забыл как страшный сон. Единственный минус — экосистема плагинов у nginx всё-таки богаче, и для совсем хитрых сценариев иногда приходится лезть в документацию. Но для домашнего реверс-прокси на десяток сервисов Caddy — это просто счастье. Жалею, что тянул с переездом так долго.]]&gt;</description>
      <content:encoded><![CDATA[<p>У меня на сервере крутится с десяток сервисов: медиатека, заметки, файлопомойка, пара админок. Все они слушают свои порты, а наружу торчит один реверс-прокси, который разруливает, кого куда пускать по доменному имени. Долгое время этим занимался nginx. Работал, не жаловался — но обслуживать его конфиги и сертификаты надоело настолько, что я в один выходной взял и переехал на Caddy.</p>

<h2 id="что-бесило-в-nginx">Что бесило в nginx</h2>

<p>Сам по себе nginx прекрасен, вопросов нет. Но вокруг него я насобирал зоопарк:</p>
<ul><li>отдельный <code>certbot</code> в cron, который выпускает и продлевает сертификаты Let&#39;s Encrypt;</li>
<li>руками прописанные пути к <code>fullchain.pem</code> и <code>privkey.pem</code> в каждом <code>server</code>-блоке;</li>
<li>бойлерплейт на редирект с 80 на 443, на <code>ssl_protocols</code>, на заголовки;</li>
<li>и классика — забыл перезагрузить nginx после продления серта, словил протухший сертификат, узнал об этом от жены, которая не смогла зайти на семейные заметки.</li></ul>

<p>Каждый новый сервис — это копипаст здоровенного <code>server</code>-блока и правка путей. К десятому разу начинаешь думать, что должен быть способ проще.</p>

<h2 id="caddyfile-который-заменил-всё">Caddyfile, который заменил всё</h2>

<p>Способ есть, и он называется Caddy. Весь мой конфиг для двух сервисов выглядит так:</p>

<pre><code class="language-caddyfile">films.example-home.lan {
    reverse_proxy localhost:8096
}

notes.example-home.lan {
    reverse_proxy localhost:8080
}
</code></pre>

<p>Всё. Это не урезанный пример — это реально рабочий минимум. Caddy сам:</p>
<ul><li>сходит в Let&#39;s Encrypt, выпустит сертификат на каждый домен;</li>
<li>будет молча его продлевать в фоне, без cron и без моего участия;</li>
<li>поднимет редирект с HTTP на HTTPS;</li>
<li>выставит вменяемые заголовки безопасности по умолчанию.</li></ul>

<p>Запускаю всё тем же compose, конфиг лежит в <code>/app/caddy/</code>:</p>

<pre><code class="language-yaml">services:
  caddy:
    image: caddy:2
    container_name: caddy
    ports:
      - &#34;80:80&#34;
      - &#34;443:443&#34;
      - &#34;443:443/udp&#34;   # HTTP/3 (QUIC)
    volumes:
      - /app/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - /app/caddy/data:/data
      - /app/caddy/config:/config
    restart: unless-stopped
</code></pre>

<p>Обратите внимание на <code>443:443/udp</code> — это для HTTP/3. Caddy умеет его из коробки, достаточно пробросить UDP-порт, и современные браузеры сами договорятся о QUIC. На спидтестах внутри локалки разницы я особо не заметил, но при доступе снаружи через мобильный интернет страницы админок стали ощутимо живее открываться.</p>

<h2 id="перезагрузка-без-даунтайма">Перезагрузка без даунтайма</h2>

<p>Добавил новый сервис — дописал три строчки в Caddyfile и сделал:</p>

<pre><code class="language-bash">docker exec caddy caddy reload --config /etc/caddy/Caddyfile
</code></pre>

<p><code>reload</code> подхватывает изменения на лету, без обрыва текущих соединений. Никакого <code>nginx -t &amp;&amp; systemctl reload</code> с замиранием сердца.</p>

<h2 id="что-в-итоге">Что в итоге</h2>

<p><code>/data</code> я смонтировал в volume не просто так — там лежат выпущенные сертификаты и ACME-аккаунт. Если его потерять, Caddy перевыпустит сертификаты заново, но можно упереться в рейт-лимиты Let&#39;s Encrypt. Так что эта папка — то, что обязательно попадает в бэкап.</p>

<p>Конфиг ужался раз в десять, certbot из крона выпилил, про протухшие сертификаты забыл как страшный сон. Единственный минус — экосистема плагинов у nginx всё-таки богаче, и для совсем хитрых сценариев иногда приходится лезть в документацию. Но для домашнего реверс-прокси на десяток сервисов Caddy — это просто счастье. Жалею, что тянул с переездом так долго.</p>
]]></content:encoded>
      <guid>https://sparn.ru/caddy-vmesto-nginx</guid>
      <pubDate>Sun, 24 Aug 2025 13:12:00 +0000</pubDate>
    </item>
    <item>
      <title>Jellyfin как домашняя медиатека: фильмы, сериалы и транскодинг через iGPU</title>
      <link>https://sparn.ru/jellyfin-mediateka</link>
      <description>&lt;![CDATA[Долгое время вся коллекция фильмов и музыки у меня жила просто папками на сетевом диске. Открываешь SMB-шару с телевизора, листаешь имена файлов вида Movie.2014.1080p.BluRay.x264.mkv и пытаешься вспомнить, что это вообще такое. Постеров нет, прогресс просмотра не сохраняется, на телефоне вообще боль. Поставил Jellyfin — и наконец перестал стыдиться собственной медиатеки.&#xA;&#xA;Jellyfin — это бесплатный медиасервер, форк старого Emby, полностью опенсорсный, без подписок и облачных аккаунтов. Он сам подтягивает метаданные, постеры, описания, раскладывает всё по библиотекам и отдаёт клиентам на любой платформе.&#xA;&#xA;Compose и проброс iGPU&#xA;&#xA;Главная фишка моего сервера (Intel i5-12400, встроенное видео UHD 730) — аппаратное транскодирование через QuickSync. Без него процессор при перекодировании 4K в реальном времени уходит в потолок и кулер начинает гудеть как пылесос. С QuickSync та же задача — это пара процентов CPU и тёплый радиатор.&#xA;&#xA;Чтобы Jellyfin в контейнере увидел iGPU, нужно пробросить /dev/dri:&#xA;&#xA;services:&#xA;  jellyfin:&#xA;    image: jellyfin/jellyfin:latest&#xA;    containername: jellyfin&#xA;    user: 1000:1000&#xA;    networkmode: host&#xA;    devices:&#xA;      /dev/dri:/dev/dri&#xA;    groupadd:&#xA;      &#34;989&#34;          # gid группы render на хосте, см. getent group render&#xA;    volumes:&#xA;      /app/jellyfin/config:/config&#xA;      /app/jellyfin/cache:/cache&#xA;      /mnt/media/films:/media/films:ro&#xA;      /mnt/media/series:/media/series:ro&#xA;      /mnt/media/music:/media/music:ro&#xA;    restart: unless-stopped&#xA;&#xA;Пара граблей, на которые я наступил, чтоб потом не гуглить то же самое:&#xA;&#xA;groupadd с правильным gid группы render обязателен, иначе контейнер видит /dev/dri, но прав на него нет. Узнать gid: getent group render.&#xA;Библиотеки монтирую :ro — Jellyfin незачем писать в коллекцию, пусть только читает.&#xA;network_mode: host упрощает работу DLNA и обнаружение клиентов в локалке.&#xA;&#xA;После старта в админке идём в Dashboard → Playback и включаем Intel QuickSync (QSV), выбираем нужные кодеки (H.264, HEVC, VP9). Проверить, что транскод реально идёт на железе, можно так:&#xA;&#xA;docker exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo&#xA;&#xA;Если выдаёт список профилей VAProfileH264 и прочие — всё ок, iGPU подхватился.&#xA;&#xA;Организация библиотек&#xA;&#xA;Тут весь секрет — правильные имена файлов и папок, тогда скрейпер не промахивается. Для фильмов:&#xA;&#xA;films/&#xA;  Дюна (2021)/Dune.2021.2160p.mkv&#xA;  Бегущий по лезвию (1982)/Blade.Runner.1982.mkv&#xA;&#xA;Для сериалов — папка сериала, внутри Season 01, файлы вида S01E03. Год в скобках критичен: без него Jellyfin путает ремейки и оригиналы. Музыку раскладываю Исполнитель/Альбом/01 - Трек.flac, теги читает из самих файлов.&#xA;&#xA;Клиенты&#xA;&#xA;На телевизоре (LG с webOS) поставил официальное приложение из стора — работает на удивление бодро. На телефоне — Findroid под Android, приятнее официального. На втором ТВ воткнул Android-приставку и гоняю Jellyfin Media Player. Прогресс синхронизируется между всеми устройствами: начал смотреть на телефоне в метро, дома продолжил с того же места на большом экране.&#xA;&#xA;Отдельный кайф — что вся семья завела свои профили, у каждого свой список «продолжить просмотр» и родительский контроль для детского профиля настраивается в два клика.&#xA;&#xA;Итог: коллекция перестала быть свалкой файлов и стала нормальным сервисом, которым реально пользуются домашние. Жалею только, что не сделал этого раньше.]]&gt;</description>
      <content:encoded><![CDATA[<p>Долгое время вся коллекция фильмов и музыки у меня жила просто папками на сетевом диске. Открываешь SMB-шару с телевизора, листаешь имена файлов вида <code>Movie.2014.1080p.BluRay.x264.mkv</code> и пытаешься вспомнить, что это вообще такое. Постеров нет, прогресс просмотра не сохраняется, на телефоне вообще боль. Поставил Jellyfin — и наконец перестал стыдиться собственной медиатеки.</p>

<p>Jellyfin — это бесплатный медиасервер, форк старого Emby, полностью опенсорсный, без подписок и облачных аккаунтов. Он сам подтягивает метаданные, постеры, описания, раскладывает всё по библиотекам и отдаёт клиентам на любой платформе.</p>

<h2 id="compose-и-проброс-igpu">Compose и проброс iGPU</h2>

<p>Главная фишка моего сервера (Intel i5-12400, встроенное видео UHD 730) — аппаратное транскодирование через QuickSync. Без него процессор при перекодировании 4K в реальном времени уходит в потолок и кулер начинает гудеть как пылесос. С QuickSync та же задача — это пара процентов CPU и тёплый радиатор.</p>

<p>Чтобы Jellyfin в контейнере увидел iGPU, нужно пробросить <code>/dev/dri</code>:</p>

<pre><code class="language-yaml">services:
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    user: 1000:1000
    network_mode: host
    devices:
      - /dev/dri:/dev/dri
    group_add:
      - &#34;989&#34;          # gid группы render на хосте, см. getent group render
    volumes:
      - /app/jellyfin/config:/config
      - /app/jellyfin/cache:/cache
      - /mnt/media/films:/media/films:ro
      - /mnt/media/series:/media/series:ro
      - /mnt/media/music:/media/music:ro
    restart: unless-stopped
</code></pre>

<p>Пара граблей, на которые я наступил, чтоб потом не гуглить то же самое:</p>
<ul><li><code>group_add</code> с правильным gid группы <code>render</code> обязателен, иначе контейнер видит <code>/dev/dri</code>, но прав на него нет. Узнать gid: <code>getent group render</code>.</li>
<li>Библиотеки монтирую <code>:ro</code> — Jellyfin незачем писать в коллекцию, пусть только читает.</li>
<li><code>network_mode: host</code> упрощает работу DLNA и обнаружение клиентов в локалке.</li></ul>

<p>После старта в админке идём в Dashboard → Playback и включаем <code>Intel QuickSync (QSV)</code>, выбираем нужные кодеки (H.264, HEVC, VP9). Проверить, что транскод реально идёт на железе, можно так:</p>

<pre><code class="language-bash">docker exec jellyfin /usr/lib/jellyfin-ffmpeg/vainfo
</code></pre>

<p>Если выдаёт список профилей VAProfileH264 и прочие — всё ок, iGPU подхватился.</p>

<h2 id="организация-библиотек">Организация библиотек</h2>

<p>Тут весь секрет — правильные имена файлов и папок, тогда скрейпер не промахивается. Для фильмов:</p>

<pre><code>films/
  Дюна (2021)/Dune.2021.2160p.mkv
  Бегущий по лезвию (1982)/Blade.Runner.1982.mkv
</code></pre>

<p>Для сериалов — папка сериала, внутри <code>Season 01</code>, файлы вида <code>S01E03</code>. Год в скобках критичен: без него Jellyfin путает ремейки и оригиналы. Музыку раскладываю <code>Исполнитель/Альбом/01 - Трек.flac</code>, теги читает из самих файлов.</p>

<h2 id="клиенты">Клиенты</h2>

<p>На телевизоре (LG с webOS) поставил официальное приложение из стора — работает на удивление бодро. На телефоне — Findroid под Android, приятнее официального. На втором ТВ воткнул Android-приставку и гоняю Jellyfin Media Player. Прогресс синхронизируется между всеми устройствами: начал смотреть на телефоне в метро, дома продолжил с того же места на большом экране.</p>

<p>Отдельный кайф — что вся семья завела свои профили, у каждого свой список «продолжить просмотр» и родительский контроль для детского профиля настраивается в два клика.</p>

<p>Итог: коллекция перестала быть свалкой файлов и стала нормальным сервисом, которым реально пользуются домашние. Жалею только, что не сделал этого раньше.</p>
]]></content:encoded>
      <guid>https://sparn.ru/jellyfin-mediateka</guid>
      <pubDate>Sun, 15 Jun 2025 19:40:00 +0000</pubDate>
    </item>
    <item>
      <title>Бэкапы по правилу 3-2-1 на restic</title>
      <link>https://sparn.ru/backup-3-2-1-restic</link>
      <description>&lt;![CDATA[Обещал в прошлый раз — рассказываю, как я бэкаплю домашний сервер. Тема нудная ровно до того момента, как у тебя умирает диск с фотками за десять лет. После этого она становится самой интересной темой в твоей жизни, но уже поздно. Так что давайте до.&#xA;&#xA;Правило 3-2-1&#xA;&#xA;Если коротко, это здравый смысл, оформленный в цифры:&#xA;&#xA;3 копии данных;&#xA;на 2 разных носителях;&#xA;1 копия — вне дома (offsite).&#xA;&#xA;У меня это раскладывается так. Оригинал живёт на сервере (раз). Бэкап едет на внешний HDD, который воткнут в сервер по USB (два, другой носитель). И копия этого репозитория уезжает offsite — на удалённое хранилище через тот же restic (три, вне дома). Если дома случится потоп, пожар или просто кто-то унесёт коробку — данные переживут.&#xA;&#xA;Почему restic&#xA;&#xA;Перепробовал я разное, остановился на restic. Один бинарь, дедупликация (одинаковые куски хранятся один раз — экономит прилично), всё шифруется на клиенте перед записью. Последнее особенно важно для offsite-копии: на удалённом хранилище лежит зашифрованный мусор, и мне всё равно, кто его теоретически увидит.&#xA;&#xA;Инициализируем репозиторий на внешнем диске один раз:&#xA;&#xA;export RESTICREPOSITORY=/mnt/backup-hdd/restic&#xA;export RESTICPASSWORD_FILE=/root/.restic-pass&#xA;restic init&#xA;&#xA;Пароль я держу в файле и отдельно записал его в надёжное место. Потеряешь пароль — потеряешь весь репозиторий, restic в этом смысле безжалостен.&#xA;&#xA;Сам бэкап&#xA;&#xA;Бэкаплю каталог /app целиком — там и compose-рецепты, и данные сервисов, и тот самый дамп БД Nextcloud, который я делаю перед этим.&#xA;&#xA;restic backup /app \&#xA;  --exclude-caches \&#xA;  --tag daily&#xA;&#xA;Чтобы репозиторий не пух до бесконечности, навожу порядок политикой хранения — оставляю разумную глубину истории и удаляю старьё:&#xA;&#xA;restic forget \&#xA;  --keep-daily 7 \&#xA;  --keep-weekly 4 \&#xA;  --keep-monthly 6 \&#xA;  --prune&#xA;&#xA;То есть последние 7 дней, 4 недели и 6 месяцев. Этого с головой, а --prune физически освобождает место от выкинутых снапшотов.&#xA;&#xA;По расписанию&#xA;&#xA;Руками такое делать нельзя — забудешь на второй неделе. У меня это systemd timer (cron тоже годится, дело вкуса), который раз в сутки ночью дёргает скрипт: сначала дамп БД, потом restic backup, потом forget --prune, а в конце пушит копию в offsite-репозиторий командой restic copy.&#xA;&#xA;Грубый набросок строки в cron, если без systemd:&#xA;&#xA;30 3   *  /usr/local/bin/backup.sh     /var/log/backup.log 2  &amp;1&#xA;&#xA;Лог обязательно, потому что молчаливый бэкап — это бэкап, про который ты не знаешь, что он сломался месяц назад.&#xA;&#xA;Самое главное: тест восстановления&#xA;&#xA;А вот теперь то, ради чего весь пост. Бэкап, который ты ни разу не восстанавливал, — это не бэкап, а гипотеза. Я регулярно (раз в пару месяцев) проверяю, что из репозитория реально достаётся файл.&#xA;&#xA;Смотрим, какие снапшоты есть:&#xA;&#xA;restic snapshots&#xA;&#xA;И разворачиваем последний во временную папку:&#xA;&#xA;restic restore latest --target /tmp/restore-test&#xA;&#xA;Дальше глазами проверяю, что внутри лежит то, что должно, и файлы не битые. Один раз так выяснил, что забыл в бэкап положить .env-файлы (исключил лишним паттерном) — и спокойно поправил это в мирное время, а не в три часа ночи под крик «всё пропало».&#xA;&#xA;Вот, собственно, и всё. Скучно, надёжно, проверяемо. Спите спокойно.]]&gt;</description>
      <content:encoded><![CDATA[<p>Обещал в прошлый раз — рассказываю, как я бэкаплю домашний сервер. Тема нудная ровно до того момента, как у тебя умирает диск с фотками за десять лет. После этого она становится самой интересной темой в твоей жизни, но уже поздно. Так что давайте до.</p>

<h2 id="правило-3-2-1">Правило 3-2-1</h2>

<p>Если коротко, это здравый смысл, оформленный в цифры:</p>
<ul><li><strong>3</strong> копии данных;</li>
<li>на <strong>2</strong> разных носителях;</li>
<li><strong>1</strong> копия — вне дома (offsite).</li></ul>

<p>У меня это раскладывается так. Оригинал живёт на сервере (раз). Бэкап едет на <strong>внешний HDD</strong>, который воткнут в сервер по USB (два, другой носитель). И копия этого репозитория уезжает <strong>offsite</strong> — на удалённое хранилище через тот же restic (три, вне дома). Если дома случится потоп, пожар или просто кто-то унесёт коробку — данные переживут.</p>

<h2 id="почему-restic">Почему restic</h2>

<p>Перепробовал я разное, остановился на <a href="https://restic.net/" rel="nofollow">restic</a>. Один бинарь, дедупликация (одинаковые куски хранятся один раз — экономит прилично), всё <strong>шифруется на клиенте</strong> перед записью. Последнее особенно важно для offsite-копии: на удалённом хранилище лежит зашифрованный мусор, и мне всё равно, кто его теоретически увидит.</p>

<p>Инициализируем репозиторий на внешнем диске один раз:</p>

<pre><code class="language-bash">export RESTIC_REPOSITORY=/mnt/backup-hdd/restic
export RESTIC_PASSWORD_FILE=/root/.restic-pass
restic init
</code></pre>

<p>Пароль я держу в файле и <strong>отдельно записал его в надёжное место</strong>. Потеряешь пароль — потеряешь весь репозиторий, restic в этом смысле безжалостен.</p>

<h2 id="сам-бэкап">Сам бэкап</h2>

<p>Бэкаплю каталог <code>/app</code> целиком — там и compose-рецепты, и данные сервисов, и тот самый дамп БД Nextcloud, который я делаю перед этим.</p>

<pre><code class="language-bash">restic backup /app \
  --exclude-caches \
  --tag daily
</code></pre>

<p>Чтобы репозиторий не пух до бесконечности, навожу порядок политикой хранения — оставляю разумную глубину истории и удаляю старьё:</p>

<pre><code class="language-bash">restic forget \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 6 \
  --prune
</code></pre>

<p>То есть последние 7 дней, 4 недели и 6 месяцев. Этого с головой, а <code>--prune</code> физически освобождает место от выкинутых снапшотов.</p>

<h2 id="по-расписанию">По расписанию</h2>

<p>Руками такое делать нельзя — забудешь на второй неделе. У меня это <strong>systemd timer</strong> (cron тоже годится, дело вкуса), который раз в сутки ночью дёргает скрипт: сначала дамп БД, потом <code>restic backup</code>, потом <code>forget --prune</code>, а в конце пушит копию в offsite-репозиторий командой <code>restic copy</code>.</p>

<p>Грубый набросок строки в cron, если без systemd:</p>

<pre><code class="language-bash">30 3 * * *  /usr/local/bin/backup.sh &gt;&gt; /var/log/backup.log 2&gt;&amp;1
</code></pre>

<p>Лог обязательно, потому что молчаливый бэкап — это бэкап, про который ты не знаешь, что он сломался месяц назад.</p>

<h2 id="самое-главное-тест-восстановления">Самое главное: тест восстановления</h2>

<p>А вот теперь то, ради чего весь пост. <strong>Бэкап, который ты ни разу не восстанавливал, — это не бэкап, а гипотеза.</strong> Я регулярно (раз в пару месяцев) проверяю, что из репозитория реально достаётся файл.</p>

<p>Смотрим, какие снапшоты есть:</p>

<pre><code class="language-bash">restic snapshots
</code></pre>

<p>И разворачиваем последний во временную папку:</p>

<pre><code class="language-bash">restic restore latest --target /tmp/restore-test
</code></pre>

<p>Дальше глазами проверяю, что внутри лежит то, что должно, и файлы не битые. Один раз так выяснил, что забыл в бэкап положить <code>.env</code>-файлы (исключил лишним паттерном) — и спокойно поправил это в мирное время, а не в три часа ночи под крик «всё пропало».</p>

<p>Вот, собственно, и всё. Скучно, надёжно, проверяемо. Спите спокойно.</p>
]]></content:encoded>
      <guid>https://sparn.ru/backup-3-2-1-restic</guid>
      <pubDate>Sun, 27 Apr 2025 15:30:00 +0000</pubDate>
    </item>
    <item>
      <title>Nextcloud дома и автозагрузка фоток с телефона</title>
      <link>https://sparn.ru/nextcloud-foto</link>
      <description>&lt;![CDATA[Главный раздражитель, который и подтолкнул меня поднять хранилище дома — это вечное «в облаке кончилось место, доплатите». Фоток с телефона за годы накопилось столько, что бесплатных гигов не хватает примерно никогда. И я подумал: у меня же стоит коробка с диском, давай-ка я буду хозяином своим фоткам.&#xA;&#xA;Поставил Nextcloud, по своему же правилу — отдельным стеком в /app/nextcloud/. Связка стандартная: сам Nextcloud + база (взял MariaDB) + Redis для кэша. Всё в одном compose.yml, данные смонтированы в ./data, которая лежит на SATA-диске под хранилище.&#xA;&#xA;Автозагрузка фото с телефона&#xA;&#xA;Ради этого всё и затевалось. На телефон ставится мобильный клиент Nextcloud, в нём включается «Автозагрузка»: выбираешь папку с камерой, и каждое новое фото само улетает на сервер по wifi. Поснимал за день — вечером дома всё уже на диске, без ручного копирования через провод.&#xA;&#xA;Я ещё включил, чтобы заливалось только по wifi и оставлял оригиналы на телефоне до подтверждения — мобильный трафик и нервы целее.&#xA;&#xA;По объёму: у меня сейчас наехало около 180 ГБ фото и видео, и это вполне комфортно лежит на отдельном диске. Видео жрёт несоизмеримо больше фоток, так что если вы снимаете 4K — закладывайте место с запасом.&#xA;&#xA;Грабли, на которые я наступил&#xA;&#xA;Без них не обошлось, записываю, чтобы не повторять.&#xA;&#xA;Права на каталог data. Классика. Контейнер Nextcloud работает под пользователем веб-сервера (uid 33, www-data), а каталог с данными после первого запуска принадлежал кому попало. Симптом — Nextcloud ругается, что не может писать. Лечится приведением владельца в порядок:&#xA;&#xA;sudo chown -R 33:33 /app/nextcloud/data&#xA;&#xA;После этого жалобы пропали. Если потом руками кидать файлы в data мимо Nextcloud — он их не увидит, пока не сделаешь files:scan, но это уже другая история.&#xA;&#xA;Генерация превью. Когда залил всю гору фоток разом, веб-морда стала открываться мучительно долго: Nextcloud пытался на лету генерировать превьюшки для тысяч картинок. Решается тем, чтобы прогнать их пачкой заранее, через occ внутри контейнера:&#xA;&#xA;docker compose exec -u www-data nextcloud \&#xA;  php occ preview:generate-all&#xA;&#xA;Один раз помучился — дальше галерея листается бодро. Тяжёлые форматы (HEIC, видео) требуют, чтобы в образе были нужные библиотеки; в актуальном официальном образе с этим уже норм.&#xA;&#xA;Приложение Memories. Поставил его поверх — это нормальная лента «фото по датам», к которой привыкаешь в облаках. Хорошая штука, но у неё свой индекс, который надо один раз прогнать (memories:index), иначе лента пустая и ты сидишь и не понимаешь, почему. Прогнал — заработало.&#xA;&#xA;Бэкап базы&#xA;&#xA;Отдельно проговорю, потому что про это любят забывать: файлы фоток — это половина дела, вторая половина живёт в базе. В ней метаданные, шаринги, кто что куда залил. Потеряешь БД — получишь кучу файлов без структуры.&#xA;&#xA;Поэтому дамп базы у меня делается отдельно и регулярно:&#xA;&#xA;docker compose exec -T db \&#xA;  mysqldump -u nextcloud -p&#34;$DB_PASS&#34; nextcloud \&#xA;    /app/nextcloud/backup/db-$(date +%F).sql&#xA;&#xA;А вот как этот дамп вместе с фотками уезжает в нормальный бэкап по правилу 3-2-1 — будет в следующем посте. Потому что хранилище без бэкапа — это не хранилище, а бомба замедленного действия.]]&gt;</description>
      <content:encoded><![CDATA[<p>Главный раздражитель, который и подтолкнул меня поднять хранилище дома — это вечное «в облаке кончилось место, доплатите». Фоток с телефона за годы накопилось столько, что бесплатных гигов не хватает примерно никогда. И я подумал: у меня же стоит коробка с диском, давай-ка я буду хозяином своим фоткам.</p>

<p>Поставил <strong>Nextcloud</strong>, по своему же правилу — отдельным стеком в <code>/app/nextcloud/</code>. Связка стандартная: сам Nextcloud + база (взял MariaDB) + Redis для кэша. Всё в одном <code>compose.yml</code>, данные смонтированы в <code>./data</code>, которая лежит на SATA-диске под хранилище.</p>

<h2 id="автозагрузка-фото-с-телефона">Автозагрузка фото с телефона</h2>

<p>Ради этого всё и затевалось. На телефон ставится мобильный клиент Nextcloud, в нём включается <strong>«Автозагрузка»</strong>: выбираешь папку с камерой, и каждое новое фото само улетает на сервер по wifi. Поснимал за день — вечером дома всё уже на диске, без ручного копирования через провод.</p>

<p>Я ещё включил, чтобы заливалось <strong>только по wifi</strong> и оставлял оригиналы на телефоне до подтверждения — мобильный трафик и нервы целее.</p>

<p>По объёму: у меня сейчас наехало <strong>около 180 ГБ</strong> фото и видео, и это вполне комфортно лежит на отдельном диске. Видео жрёт несоизмеримо больше фоток, так что если вы снимаете 4K — закладывайте место с запасом.</p>

<h2 id="грабли-на-которые-я-наступил">Грабли, на которые я наступил</h2>

<p>Без них не обошлось, записываю, чтобы не повторять.</p>

<p><strong>Права на каталог <code>data</code>.</strong> Классика. Контейнер Nextcloud работает под пользователем веб-сервера (uid 33, <code>www-data</code>), а каталог с данными после первого запуска принадлежал кому попало. Симптом — Nextcloud ругается, что не может писать. Лечится приведением владельца в порядок:</p>

<pre><code class="language-bash">sudo chown -R 33:33 /app/nextcloud/data
</code></pre>

<p>После этого жалобы пропали. Если потом руками кидать файлы в <code>data</code> мимо Nextcloud — он их не увидит, пока не сделаешь <code>files:scan</code>, но это уже другая история.</p>

<p><strong>Генерация превью.</strong> Когда залил всю гору фоток разом, веб-морда стала открываться мучительно долго: Nextcloud пытался на лету генерировать превьюшки для тысяч картинок. Решается тем, чтобы прогнать их пачкой заранее, через occ внутри контейнера:</p>

<pre><code class="language-bash">docker compose exec -u www-data nextcloud \
  php occ preview:generate-all
</code></pre>

<p>Один раз помучился — дальше галерея листается бодро. Тяжёлые форматы (HEIC, видео) требуют, чтобы в образе были нужные библиотеки; в актуальном официальном образе с этим уже норм.</p>

<p><strong>Приложение Memories.</strong> Поставил его поверх — это нормальная лента «фото по датам», к которой привыкаешь в облаках. Хорошая штука, но у неё свой индекс, который надо один раз прогнать (<code>memories:index</code>), иначе лента пустая и ты сидишь и не понимаешь, почему. Прогнал — заработало.</p>

<h2 id="бэкап-базы">Бэкап базы</h2>

<p>Отдельно проговорю, потому что про это любят забывать: <strong>файлы фоток — это половина дела, вторая половина живёт в базе.</strong> В ней метаданные, шаринги, кто что куда залил. Потеряешь БД — получишь кучу файлов без структуры.</p>

<p>Поэтому дамп базы у меня делается отдельно и регулярно:</p>

<pre><code class="language-bash">docker compose exec -T db \
  mysqldump -u nextcloud -p&#34;$DB_PASS&#34; nextcloud \
  &gt; /app/nextcloud/backup/db-$(date +%F).sql
</code></pre>

<p>А вот как этот дамп вместе с фотками уезжает в нормальный бэкап по правилу 3-2-1 — будет в следующем посте. Потому что хранилище без бэкапа — это не хранилище, а бомба замедленного действия.</p>
]]></content:encoded>
      <guid>https://sparn.ru/nextcloud-foto</guid>
      <pubDate>Sat, 22 Mar 2025 16:48:00 +0000</pubDate>
    </item>
    <item>
      <title>Как я раскладываю сервисы по docker compose</title>
      <link>https://sparn.ru/docker-compose-struktura</link>
      <description>&lt;![CDATA[Когда сервисов на коробке становится больше трёх, наступает момент истины: либо у тебя есть система, либо у тебя бардак, в котором ты через полгода не вспомнишь, где чьи данные. У меня бардак уже был, поэтому в этот раз я завёл правило и держусь за него зубами.&#xA;&#xA;Правило ровно одно: один docker compose стек = одна папка в /app/сервис. Всё, что относится к сервису, живёт внутри этой папки и нигде больше.&#xA;&#xA;Как выглядит на диске&#xA;&#xA;/app&#xA;├── writefreely/&#xA;│   ├── compose.yml&#xA;│   ├── .env&#xA;│   └── data/&#xA;├── nextcloud/&#xA;│   ├── compose.yml&#xA;│   ├── .env&#xA;│   └── data/&#xA;└── gitea/&#xA;    ├── compose.yml&#xA;    ├── .env&#xA;    └── data/&#xA;&#xA;Никаких именованных докер-томов, разбросанных в недрах /var/lib/docker, которые потом фиг найдёшь. Все данные я монтирую относительными путями внутрь папки сервиса — в подкаталог data/. Хочу посмотреть, что сервис нажил — иду в его папку, и там всё.&#xA;&#xA;Из чего состоит стек&#xA;&#xA;Типичный compose.yml у меня выглядит так:&#xA;&#xA;services:&#xA;  writefreely:&#xA;    image: writeas/writefreely:latest&#xA;    containername: writefreely&#xA;    restart: unless-stopped&#xA;    envfile: .env&#xA;    ports:&#xA;      &#34;127.0.0.1:8080:8080&#34;&#xA;    volumes:&#xA;      ./data:/data&#xA;&#xA;Несколько вещей, которые я делаю всегда и осознанно:&#xA;&#xA;restart: unless-stopped на каждом сервисе. После ребута коробки (или скачка питания) всё поднимается само. Но если я сам остановил контейнер руками — он остаётся остановленным и не воскресает у меня за спиной. always ведёт себя навязчивее, поэтому именно unless-stopped.&#xA;Все секреты — в .env, не в compose.yml. Пароли БД, токены, ключи. Сам compose.yml тогда не стыдно показать кому угодно.&#xA;Порты вешаю на 127.0.0.1, наружу публикую отдельно через единую точку входа. Внутрь докера снаружи напрямую никто не лезет.&#xA;&#xA;.env рядом, и в нём примерно вот это:&#xA;&#xA;/app/writefreely/.env&#xA;DOMAIN=blog.local&#xA;DATABASE_PASSWORD=...&#xA;&#xA;Восстанавливаемость — ради неё всё и затевалось&#xA;&#xA;Главная мысль всей этой раскладки: коробку я должен уметь собрать заново с нуля за вечер. Не «вспомнить, как было», а буквально развернуть.&#xA;&#xA;Для этого мне нужны две вещи: сами compose.yml с .env (это рецепт) и data/ (это содержимое). Рецепты весят копейки и меняются редко, поэтому я их версионирую и складываю отдельно. Грубо — собрать все compose-файлы в одно место:&#xA;&#xA;mkdir -p ~/server-config/app&#xA;rsync -av --include=&#39;/&#39; \&#xA;  --include=&#39;compose.yml&#39; --include=&#39;.env&#39; \&#xA;  --exclude=&#39;&#39; \&#xA;  /app/ ~/server-config/app/&#xA;&#xA;Эту папку ~/server-config я кладу в git и в бэкап. Получается, что вся конфигурация сервера — это десяток текстовых файлов, которые читаются глазами и восстанавливаются за минуты.&#xA;&#xA;Данные из data/ — отдельная история, они большие и про них будет отдельный разговор про бэкапы. Но рецепт от содержимого я держу раздельно сознательно: потерять конфиг и потерять данные — это две очень разные катастрофы, и готовиться к ним надо по-разному.&#xA;&#xA;Скучно? Скучно. Но именно скучные системы переживают переезд на новое железо без седых волос.]]&gt;</description>
      <content:encoded><![CDATA[<p>Когда сервисов на коробке становится больше трёх, наступает момент истины: либо у тебя есть система, либо у тебя бардак, в котором ты через полгода не вспомнишь, где чьи данные. У меня бардак уже был, поэтому в этот раз я завёл правило и держусь за него зубами.</p>

<p>Правило ровно одно: <strong>один docker compose стек = одна папка в <code>/app/&lt;сервис&gt;</code></strong>. Всё, что относится к сервису, живёт внутри этой папки и нигде больше.</p>

<h2 id="как-выглядит-на-диске">Как выглядит на диске</h2>

<pre><code class="language-text">/app
├── writefreely/
│   ├── compose.yml
│   ├── .env
│   └── data/
├── nextcloud/
│   ├── compose.yml
│   ├── .env
│   └── data/
└── gitea/
    ├── compose.yml
    ├── .env
    └── data/
</code></pre>

<p>Никаких именованных докер-томов, разбросанных в недрах <code>/var/lib/docker</code>, которые потом фиг найдёшь. Все данные я монтирую <strong>относительными путями внутрь папки сервиса</strong> — в подкаталог <code>data/</code>. Хочу посмотреть, что сервис нажил — иду в его папку, и там всё.</p>

<h2 id="из-чего-состоит-стек">Из чего состоит стек</h2>

<p>Типичный <code>compose.yml</code> у меня выглядит так:</p>

<pre><code class="language-yaml">services:
  writefreely:
    image: writeas/writefreely:latest
    container_name: writefreely
    restart: unless-stopped
    env_file: .env
    ports:
      - &#34;127.0.0.1:8080:8080&#34;
    volumes:
      - ./data:/data
</code></pre>

<p>Несколько вещей, которые я делаю всегда и осознанно:</p>
<ul><li><strong><code>restart: unless-stopped</code></strong> на каждом сервисе. После ребута коробки (или скачка питания) всё поднимается само. Но если я сам остановил контейнер руками — он остаётся остановленным и не воскресает у меня за спиной. <code>always</code> ведёт себя навязчивее, поэтому именно <code>unless-stopped</code>.</li>
<li><strong>Все секреты — в <code>.env</code></strong>, не в <code>compose.yml</code>. Пароли БД, токены, ключи. Сам <code>compose.yml</code> тогда не стыдно показать кому угодно.</li>
<li><strong>Порты вешаю на <code>127.0.0.1</code></strong>, наружу публикую отдельно через единую точку входа. Внутрь докера снаружи напрямую никто не лезет.</li></ul>

<p><code>.env</code> рядом, и в нём примерно вот это:</p>

<pre><code class="language-bash"># /app/writefreely/.env
DOMAIN=blog.local
DATABASE_PASSWORD=...
</code></pre>

<h2 id="восстанавливаемость-ради-неё-всё-и-затевалось">Восстанавливаемость — ради неё всё и затевалось</h2>

<p>Главная мысль всей этой раскладки: <strong>коробку я должен уметь собрать заново с нуля за вечер.</strong> Не «вспомнить, как было», а буквально развернуть.</p>

<p>Для этого мне нужны две вещи: сами <code>compose.yml</code> с <code>.env</code> (это рецепт) и <code>data/</code> (это содержимое). Рецепты весят копейки и меняются редко, поэтому я их версионирую и складываю отдельно. Грубо — собрать все compose-файлы в одно место:</p>

<pre><code class="language-bash">mkdir -p ~/server-config/app
rsync -av --include=&#39;*/&#39; \
  --include=&#39;compose.yml&#39; --include=&#39;.env&#39; \
  --exclude=&#39;*&#39; \
  /app/ ~/server-config/app/
</code></pre>

<p>Эту папку <code>~/server-config</code> я кладу в git и в бэкап. Получается, что вся конфигурация сервера — это десяток текстовых файлов, которые читаются глазами и восстанавливаются за минуты.</p>

<p>Данные из <code>data/</code> — отдельная история, они большие и про них будет отдельный разговор про бэкапы. Но рецепт от содержимого я держу раздельно сознательно: потерять конфиг и потерять данные — это две очень разные катастрофы, и готовиться к ним надо по-разному.</p>

<p>Скучно? Скучно. Но именно скучные системы переживают переезд на новое железо без седых волос.</p>
]]></content:encoded>
      <guid>https://sparn.ru/docker-compose-struktura</guid>
      <pubDate>Sun, 23 Feb 2025 17:05:00 +0000</pubDate>
    </item>
    <item>
      <title>Домашний сервер из мини-ПК</title>
      <link>https://sparn.ru/domashniy-server-mini-pc</link>
      <description>&lt;![CDATA[Обещал написать про железо — пишу. Спойлер: ничего пафосного, и это сознательно.&#xA;&#xA;Долгое время «домашний сервер» у меня жил в голове как стойка, гул вентиляторов и счёт за электричество размером с аренду. Потом я посмотрел, сколько реально потребляет то, что мне нужно, и понял, что городить огород незачем. Мне нужна тихая коробка, которая работает 24/7, не греет комнату и не пугает соседей по счётчику.&#xA;&#xA;Что в итоге взял&#xA;&#xA;Взял б/у Dell OptiPlex Micro — это такой мини-корпус размером чуть больше книжки. На вторичке их полно, потому что корпорации списывают их пачками после офисного цикла. Внутри обычный десктопный x86, а не мобильный огрызок.&#xA;&#xA;Конфигурация после небольшого апгрейда:&#xA;&#xA;CPU: Intel Core i5 (4 ядра, вполне хватает);&#xA;RAM: было 8 ГБ, докинул до 16 ГБ SO-DIMM (минут пять работы);&#xA;системный диск: NVMe 512 ГБ в M.2 слот;&#xA;плюс одна SATA 2.5&#34; под данные — в этих корпусах есть посадочное место под 2.5&#34; диск, чем я и воспользовался.&#xA;&#xA;Альтернативой я всерьёз рассматривал свежие мини-ПК на Intel N100 — они новые, холодные, и идут с нормальным питанием из коробки. Если не хочется возиться с б/у, это отличный вариант, по деньгам сопоставимо. Я просто люблю запах списанного корпоративного железа.&#xA;&#xA;Почему не Raspberry Pi&#xA;&#xA;Этот вопрос мне задают чаще всего, поэтому отвечу один раз и со ссылкой на этот абзац.&#xA;&#xA;«Малинку» я очень уважаю, но для домашнего сервера она мне не зашла по трём причинам:&#xA;&#xA;Архитектура. Тут x86-64, а значит весь софт и все докер-образы просто работают. Не надо искать arm-сборки и спотыкаться об «а вот это под arm не собрали».&#xA;Диски. NVMe и SATA напрямую, а не microSD, которая дохнет от постоянной записи, и не USB-костыли. Для хранилища это критично.&#xA;Цена. Бэушный мини-ПК с уже стоящей памятью и местом под диск выходит сопоставимо с Pi в сборе (плата + нормальный корпус + питание + накопитель), но сразу даёт полноценную машину.&#xA;&#xA;Pi прекрасен как маленький контроллер чего-нибудь. Но как рабочая лошадка с дисками — мини-ПК удобнее.&#xA;&#xA;Потребление&#xA;&#xA;Главный приятный сюрприз — аппетит. Замерил ваттметром из розетки:&#xA;&#xA;в простое — около 10–15 Вт;&#xA;под нагрузкой (когда что-то реально молотит) — поднимается до ~30–35 Вт, но это редко.&#xA;&#xA;15 Вт в простое — это меньше, чем светодиодная лампочка в люстре. За такое не жалко платить за круглосуточную работу.&#xA;&#xA;Система&#xA;&#xA;Поставил Ubuntu Server 24.04 LTS. Без графики, чисто консоль. Выбор простой: LTS до 2029-го, гигантское комьюнити, любой гайд в интернете по умолчанию написан под Ubuntu/Debian. Когда что-то ломается, ответ находится за тридцать секунд.&#xA;&#xA;Разметку сделал максимально скучную: NVMe под систему, SATA-диск отдельным разделом под данные сервисов, который потом монтирую в докер-тома. Всё, дальше уже софт.&#xA;&#xA;В следующий раз напишу, как раскладываю сервисы по докеру, чтобы этот ящик можно было собрать заново за вечер, а не за выходные.]]&gt;</description>
      <content:encoded><![CDATA[<p>Обещал написать про железо — пишу. Спойлер: ничего пафосного, и это сознательно.</p>

<p>Долгое время «домашний сервер» у меня жил в голове как стойка, гул вентиляторов и счёт за электричество размером с аренду. Потом я посмотрел, сколько реально потребляет то, что мне нужно, и понял, что городить огород незачем. Мне нужна тихая коробка, которая работает 24/7, не греет комнату и не пугает соседей по счётчику.</p>

<h2 id="что-в-итоге-взял">Что в итоге взял</h2>

<p>Взял б/у <strong>Dell OptiPlex Micro</strong> — это такой мини-корпус размером чуть больше книжки. На вторичке их полно, потому что корпорации списывают их пачками после офисного цикла. Внутри обычный десктопный x86, а не мобильный огрызок.</p>

<p>Конфигурация после небольшого апгрейда:</p>
<ul><li>CPU: Intel Core i5 (4 ядра, вполне хватает);</li>
<li>RAM: было 8 ГБ, докинул до <strong>16 ГБ</strong> SO-DIMM (минут пять работы);</li>
<li>системный диск: <strong>NVMe 512 ГБ</strong> в M.2 слот;</li>
<li>плюс одна <strong>SATA 2.5”</strong> под данные — в этих корпусах есть посадочное место под 2.5” диск, чем я и воспользовался.</li></ul>

<p>Альтернативой я всерьёз рассматривал свежие мини-ПК на <strong>Intel N100</strong> — они новые, холодные, и идут с нормальным питанием из коробки. Если не хочется возиться с б/у, это отличный вариант, по деньгам сопоставимо. Я просто люблю запах списанного корпоративного железа.</p>

<h2 id="почему-не-raspberry-pi">Почему не Raspberry Pi</h2>

<p>Этот вопрос мне задают чаще всего, поэтому отвечу один раз и со ссылкой на этот абзац.</p>

<p>«Малинку» я очень уважаю, но для домашнего сервера она мне не зашла по трём причинам:</p>
<ol><li><strong>Архитектура.</strong> Тут x86-64, а значит весь софт и все докер-образы просто работают. Не надо искать arm-сборки и спотыкаться об «а вот это под arm не собрали».</li>
<li><strong>Диски.</strong> NVMe и SATA напрямую, а не microSD, которая дохнет от постоянной записи, и не USB-костыли. Для хранилища это критично.</li>
<li><strong>Цена.</strong> Бэушный мини-ПК с уже стоящей памятью и местом под диск выходит сопоставимо с Pi в сборе (плата + нормальный корпус + питание + накопитель), но сразу даёт полноценную машину.</li></ol>

<p>Pi прекрасен как маленький контроллер чего-нибудь. Но как рабочая лошадка с дисками — мини-ПК удобнее.</p>

<h2 id="потребление">Потребление</h2>

<p>Главный приятный сюрприз — аппетит. Замерил ваттметром из розетки:</p>
<ul><li>в простое — <strong>около 10–15 Вт</strong>;</li>
<li>под нагрузкой (когда что-то реально молотит) — поднимается до ~30–35 Вт, но это редко.</li></ul>

<p>15 Вт в простое — это меньше, чем светодиодная лампочка в люстре. За такое не жалко платить за круглосуточную работу.</p>

<h2 id="система">Система</h2>

<p>Поставил <strong>Ubuntu Server 24.04 LTS</strong>. Без графики, чисто консоль. Выбор простой: LTS до 2029-го, гигантское комьюнити, любой гайд в интернете по умолчанию написан под Ubuntu/Debian. Когда что-то ломается, ответ находится за тридцать секунд.</p>

<p>Разметку сделал максимально скучную: NVMe под систему, SATA-диск отдельным разделом под данные сервисов, который потом монтирую в докер-тома. Всё, дальше уже софт.</p>

<p>В следующий раз напишу, как раскладываю сервисы по докеру, чтобы этот ящик можно было собрать заново за вечер, а не за выходные.</p>
]]></content:encoded>
      <guid>https://sparn.ru/domashniy-server-mini-pc</guid>
      <pubDate>Sun, 19 Jan 2025 18:34:00 +0000</pubDate>
    </item>
  </channel>
</rss>