Я убил свой домашний NAS одной кнопкой в веб-панели — и пересобрал его правильно

от автора

Часть 2. Как «Сбросить права доступа» в один клик уносит всю систему, почему я переехал на Proxmox с контейнерами и как менял сетевую папку на Nextcloud с Immich.

Что было в первой части

Это вторая часть. Первую — про то, как из мёртвого ноутбука получился рабочий NAS, — можно прочитать здесь. Если коротко: у девушки кончалось место на айфоне, покупать iCloud или новый телефон не хотелось, а в шкафу лежал дохлый ноутбук HP 15. Я снёс с него Windows, поставил Debian и OpenMediaVault, поднял сетевую папку по SMB, прикрутил бота в Telegram для управления и обошёл блокировку Telegram через Tailscale. Сто гигабайт фоток переехали с телефона на диск, всё работало, я был доволен собой.

Ровно до того дня, когда я одним кликом в веб-панели снёс всю систему. Эта часть — про катастрофу, разбор полётов и про то, как я пересобрал хранилище так, чтобы повторить такое стало физически нельзя.

Одна кнопка в веб-панели — и сервера нет

В OpenMediaVault у каждой общей папки есть кнопка «Сбросить права доступа» (Reset Permissions). Штука полезная: если намудрил с доступами, она вернёт владельца и права на папку к норме. Я и нажал — мне казалось, что чиню мелкую проблему с доступом к шаре.

Но кнопка была только спусковым крючком. Настоящую мину я заложил ещё на этапе установки, сам того не понимая. Диск в ноуте один, и установщик OpenMediaVault честно занял его целиком под систему — отдельного диска под данные у меня не было. А дальше я сделал то, чего делать нельзя: в качестве рабочего пространства для шары выбрал этот же основной SSD, прямо рядом с корнем системы. OMV-то рассчитан на то, что система живёт на одном диске, а данные — на другом; я же свалил всё в одну кучу на единственный накопитель.

В итоге путь моей шары пересекался с корнем файловой системы. И когда я нажал «Сбросить права», OpenMediaVault честно сделал то, что я попросил, — рекурсивно. Только «рекурсивно от корня шары» в моём случае означало «рекурсивно по всей системе»:

# что фактически прошлось по всему диску:

chown -R tim:users /     # владельцем всех файлов стал я, не root

chmod -R 2775 /          # права переписаны везде, setuid слетел

 

Через пару секунд система перестала быть системой.

Масштаб бедствия

Когда я попытался что-то сделать, выяснилось, что сломано примерно всё:

—   Владельцем всех файлов стал пользователь tim — включая /etc/passwd, /usr/bin/sudo и /bin/su, которые обязаны принадлежать root.

—   chown заодно сбросил бит setuid. Именно он позволяет sudo и su на секунду стать root. Без него обе команды просто перестали повышать права — а значит, починить систему «изнутри» обычным способом уже нельзя.

—   Пользователь tim к тому же не состоял в группе sudo. Замкнутый круг: чтобы дать права, нужны права.

—   Tailscale ушёл в состояние NoState — демон не мог писать свой служебный файл.

—   Только в каталогах с программами больше полутора тысяч файлов перестали принадлежать root.

Бит setuid — это пометка на программе «выполняйся с правами владельца файла, а не того, кто запустил». У sudo владелец — root, поэтому обычный пользователь через sudo на миг получает root. Рекурсивный chmod снёс эту пометку со всего, что было на диске, и обезоружил главный инструмент восстановления.

Чинить или переехать

Технически система не умерла насмерть. Можно было загрузиться в обход обычного входа: в загрузчике GRUB дописать ядру init=/bin/bash, попасть в root-консоль, вручную вернуть владельца и setuid файлам авторизации, а потом массово переустановить пакеты, чтобы вернуть остальным файлам правильные права. Долго, муторно, но реально.

Я сел, прикинул — и решил не чинить. Потому что чинить я бы стал последствие, а причина была глубже и куда неприятнее. Дело не в одной злополучной кнопке. Корень был в том, как всё было устроено с самого начала: установщик OMV занял единственный диск под систему, а я поверх этого выбрал тот же основной SSD рабочим пространством для данных. Система и шара оказались в одном слое без единой перегородки — и один кривой клик в одной службе уехал по всему диску и снёс ОС. Это не невезение, это архитектура, которая только и ждала повода. Лечить надо было её.

Так я пришёл к тому, с чего, наверное, стоило начинать, — к Proxmox и контейнерам.

Почему Proxmox и контейнеры

Proxmox VE — это система, которая превращает один физический компьютер в хост для виртуалок и контейнеров с удобной веб-панелью. Идея простая: не держать все сервисы в одной куче на голой системе, а посадить каждый в свою изолированную коробку (контейнер LXC). Падает или сходит с ума один сервис — остальные и сам хост этого даже не замечают.

Вот что я на этот раз решил для себя твёрдо:

—   Каждый сервис — в отдельном контейнере. Ошибка в одном не трогает ни хост, ни соседей. Та самая перегородка, которой не хватило в прошлый раз.

—   Тома с данными — фиксированного размера (квота). Это закрывает вторую половину прошлой аварии: разросшаяся шара теперь не сможет забить диск хоста, ей выделен жёсткий лимит.

—   Файловая система — ext4 на LVM, без ZFS. ZFS прожорлив до памяти, а у ноута её всего 12 ГБ. Снапшоты контейнеров на LVM при этом всё равно есть.

—   NAS — контейнером, а не виртуалкой с проброшенным диском: диск в ноуте один и он загрузочный, пробросить его целиком нельзя.

—   Никаких VPS и ничего наружу в публичный интернет. Принципиально.

Главный урок прошлой части одной строкой: не держать данные на системном диске рядом с корнем и не доверять «умным» кнопкам, которые рекурсивно правят права. Установщик OMV забрал весь единственный диск под систему, а я ещё и выбрал его рабочим пространством для шары — вот связка, которая и убила сервер. Изоляция по контейнерам делает так, что даже если я снова нажму не ту кнопку, разлетится максимум один контейнер.

Грабли по дороге: драйвер и Wi-Fi-мост

Веб-панель Proxmox VE — список контейнеров на узле lab.

Веб-панель Proxmox VE — список контейнеров на узле lab.

Веб-панель Proxmox VE — список контейнеров на узле lab.

Установка не обошлась без приключений. Установщик Proxmox упорно показывал сетевой порт ноута как «погасший», хотя кабель был воткнут и линк на роутере горел. Это известная болячка драйвера сетевой карты Realtek на ноутбуках: в среде установщика он не видел несущую. Лечится грубо — я доустановил систему вообще без сети, вбив сетевые настройки руками, а после первой же нормальной загрузки порт поднялся сам. В рабочей системе проблемы не было, баг жил только в установщике.

Вторая засада — физика. Роутер далеко, тянуть через всю квартиру витую пару неудобно, а выводить сервер в сеть через Wi-Fi ноутбука нельзя: Wi-Fi-клиент не отдаёт мост, и контейнеры остались бы без сети. Решение нашлось из подручного: взял роутер Zyxel и поставил его Wi-Fi-мостом. Ноут воткнут кабелем в LAN-порт Zyxel, а сам Zyxel цепляется к домашнему Wi-Fi и отдаёт ноуту проводной интернет. Подсеть совпала с домашней, так что двойного NAT не возникло — всё видят друг друга напрямую.

Пивот: от сетевой папки к нормальному облаку

Пока я пересобирал фундамент, поменялась и цель. В первой версии была просто сетевая папка по SMB — закинул файлы, забрал файлы. Но хотелось большего: единый доступ с Windows и с айфона и полноценная замена iCloud Фото с автозагрузкой, альбомами и поиском. Сетевая папка так не умеет.

Immich мне, кстати, посоветовали в комментариях к первой части — сразу несколько человек назвали его лучшей заменой iCloud Фото. Совет оказался в точку, его я и взял под фотографии. Но одними снимками дело не ограничивалось: рядом с фото хотелось держать и обычные файлы — документы, PDF, всякие рабочие архивы, то, что нужно открыть и с ноутбука, и с телефона. А Immich так не умеет: он заточен строго под фото и видео и в файловое хранилище не превращается. Поэтому я не стал валить всё в один сервис, а поставил рядом второй — Nextcloud под файлы, пока Immich остаётся специалистом по фотографиям. Каждый делает одно дело и делает его хорошо.

Поэтому Samba-контейнер я вывел из эксплуатации (реальных данных в нём ещё не было) и разнёс задачу на две специализированные коробки:

—   Nextcloud — личное файловое облако. На Windows подключается клиентом или как WebDAV-диск, на iPhone — приложением. Это замена «общей папки», только удобнее.

—   Immich — отдельное хранилище фото и видео, прямой аналог iCloud Фото: приложение на айфоне само заливает снимки в фоне, есть лента, альбомы и поиск.

Nextcloud я поднял из готового рецепта (community-scripts), отдал контейнеру 150 ГБ под данные и закрыл его в свою квоту. Не обошлось без пляски с активацией — мастер первой настройки зациклился, и его пришлось аккуратно обойти, включив правильный веб-конфиг руками, — но в итоге облако встало, клиенты на Windows и iPhone подключились, файлы видны.

Nextcloud в браузере: те же файлы видны с Windows-клиента и приложения на iPhone.

Nextcloud в браузере: те же файлы видны с Windows-клиента и приложения на iPhone.

Nextcloud в браузере: те же файлы видны с Windows-клиента и приложения на iPhone.

Immich и снова блокировки

А вот Immich напомнил, что часть нужных ему ресурсов попросту недоступна — заблокирована по определённым причинам, как и Telegram в первой части. Immich официально ставится только через Docker Compose. Я сначала пошёл по «родному» скрипту установки — и он раз за разом падал на скачивании зависимостей. Не из-за кривых рук, а потому что часть зарубежных пакет-хостов заблокирована: один компонент тянется с лондонского зеркала и отваливается по таймауту, другой висит на нуле килобайт в секунду. Классический whack-a-mole: затыкаешь один источник — спотыкаешься о следующий.

Я померил скорость до разных источников и выбрал тот, что работает стабильно. Оказалось, инфраструктура GitHub (откуда берутся официальные образы Immich) и зеркало реестра отдают честные полмегабайта в секунду без обрывов. Значит, надёжнее не «родной» скрипт с десятком хостов, а ручной путь через Docker с образами оттуда, что не блокируется.

Чтобы внутри контейнера вообще хоть что-то качалось, пришлось поправить три вещи — и каждая из них стоила мне отдельного вечера:

# 1. DNS: контейнер унаследовал недоступный ему адрес —

#    переключаем на роутер

pct set <id> --nameserver 192.168.0.1

# 2. IPv6: соединения залипали на нём до таймаута — гасим

net.ipv6.conf.all.disable_ipv6 = 1

# 3. Реестр Docker троттлит — подставляем зеркало

{ "registry-mirrors": ["https://mirror.gcr.io"] }

 

После этих фиксов Immich собрался и поехал: сервер, база и кэш живые, айфон заливает фото в фоне. Умный поиск и распознавание лиц (та часть, что тянет ML-модели с заблокированного хоста) пока отключены — это не мешает заливать и смотреть, а руки до них дойдут отдельно.

Веб-интерфейс Immich: альбомы и счётчик хранилища. Снимки с айфона заливаются в фоне, как в iCloud Фото.

Веб-интерфейс Immich: альбомы и счётчик хранилища. Снимки с айфона заливаются в фоне, как в iCloud Фото.

Веб-интерфейс Immich: альбомы и счётчик хранилища. Снимки с айфона заливаются в фоне, как в iCloud Фото.

Доступ откуда угодно без дыр в роутере

Оставался вопрос: как дотянуться до облака и фоток не только из дома. Очевидный путь — пробросить порты на роутере или поднять туннель наружу — я сознательно отверг. Публичный IP у меня, кстати, белый, проброс был бы возможен, но выставлять свой домашний сервер в открытый интернет ради удобства мне не хочется: это лишняя поверхность для атаки.

Вместо этого — снова Tailscale, но уже в роли «маршрутизатора подсети» (subnet-router). Хост анонсирует в мою приватную сеть всю домашнюю подсеть, и тогда телефон с ноутбуком через Tailscale видят и Nextcloud, и Immich по их обычным локальным адресам — откуда угодно, по одному и тому же URL. Наружу при этом не торчит ничего.

# на хосте: разрешить форвардинг и анонсировать домашнюю подсеть

echo 'net.ipv4.ip_forward=1' | sudo tee /etc/sysctl.d/99-tailscale.conf

sudo tailscale up --advertise-routes=192.168.0.0/24

# затем одобрить маршрут в админке Tailscale

 

Админка Tailscale: узел lab-1 раздаёт маршрут подсети (бейдж Subnets), десктоп работает exit-node, оба айфона в сети.

Админка Tailscale: узел lab-1 раздаёт маршрут подсети (бейдж Subnets), десктоп работает exit-node, оба айфона в сети.

Админка Tailscale: узел lab-1 раздаёт маршрут подсети (бейдж Subnets), десктоп работает exit-node, оба айфона в сети.

Есть нюанс честности ради: ближайший узел-ретранслятор Tailscale у меня в Варшаве, и вне дома по сотовой связи соединение иногда идёт через него — то есть с задержкой. Поэтому модель такая: дома Tailscale на телефоне выключен, и бэкап фоток летит напрямую по Wi-Fi на полной скорости; вне дома — включён, чтобы был доступ. Удобно и быстро там, где это важнее всего.

Самый важный урок: один диск — это не бэкап

И вот тут — главное предупреждение, которое я повторю себе и вам жирным шрифтом. Диск в ноуте по-прежнему один. Это значит, что фотографии и файлы существуют в единственном экземпляре. Я строю замену iCloud — но у Apple за кадром есть избыточность и копии, а у меня пока нет.

Из этого следует железное правило, пока не появится второй диск: не удалять оригиналы фото с телефона. Сам айфон сейчас и работает второй копией. Полноценный бэкап (регулярная копия библиотеки Immich и данных Nextcloud на отдельный диск) я сознательно отложил до того момента, когда этот второй диск появится. И только когда копия реально заработает, можно будет чистить телефон. Удалить раньше — значит наступить на те же грабли, что и с «Сбросить права», только теперь ценой станут те самые сто гигабайт воспоминаний.

Что в итоге

Сейчас на том же дохлом ноутбуке вместо одной хрупкой системы живёт аккуратный набор изолированных контейнеров:

Контейнер

Что внутри

Зачем

nextcloud

NextCloudPi: файлы, WebDAV

общая папка для Windows и iPhone

immich

Immich в Docker

фото и видео, замена iCloud

(в планах) n8n

оркестратор автоматизаций

вторая копия фото на будущее

(в планах) finance

Sure (форк Maybe Finance)

домашние финансы

 

Железо — то же самое, бесплатное (+ старый роутер). Софт — весь open-source. Зато подход поменялся полностью: вместо «всё в одной куче, один клик до катастрофы» теперь каждый сервис в своей коробке с лимитом на диск, а доступ наружу закрыт приватной сетью. За урок я заплатил целой системой. Дорого, но теперь хотя бы понимаю, за что плачу: чтобы следующий мой кривой клик стоил одного контейнера, а не всего сервера.

 

ссылка на оригинал статьи https://habr.com/ru/articles/1048754/