Локальные сервисы и офлайн-знания
В теме 7 были рассмотрены принципы подготовки и восстановления автономной операционной системы и организация доверенной цепочки обновлений через изолированный барьер. Однако сама по себе работоспособная операционная система — это лишь фундамент. Чтобы вычислительный узел при наступлении любого из сценариев отказа (от кратковременного веерного отключения до многонедельной полной изоляции, определённых в теме 1) обеспечивал полноценную деятельность людей, на нём должен функционировать прикладной сервисный комплекс.
В обычной сетевой среде пользователь даже не задумывается о том, сколько скрытых внешних зависимостей задействует простейшее действие: ввод адреса в браузере инициирует запросы к публичным рекурсивным DNS-резолверам; открытие рабочей документации обращается к облачным хранилищам; поиск технической информации направляется в глобальные поисковые системы; запуск программного кода требует проверки временных меток в сертификатах TLS и связи с серверами точного времени NTP; а координация группы опирается на централизованные мессенджеры. При исчезновении внешнего интернет-канала вся эта многослойная пирамида мгновенно рушится. Браузер выводит ошибку поиска DNS, системные утилиты зависают на десятки секунд в тщетных попытках связаться с недоступными внешними IP-адресами, локальные сессии блокируются из-за расхождения системных часов, а инженеры лишаются доступа к схемам, даташитам и руководствам по ремонту оборудования.
Тема посвящена созданию автономного сервисного узла — «Островка» (англ. Island node), развёрнутого на базе единственной физической машины и способного полностью заменить внешнюю сетевую инфраструктуру в пределах локального сегмента. Рассматривается полный спектр базовых служб: автономное разрешение имён и адресация, организация легковесного совместного доступа к файлам, хранение и мгновенный поиск по терабайтному корпусу офлайн-знаний (энциклопедии Kiwix, инженерная документация, векторные геодезические карты), инструменты децентрализованной координации и версионирования исходного кода (локальный Git, чат IRC), а также независимая синхронизация времени как фундаментальное условие информационной безопасности.
Изложение построено от базовых сетевых служб к прикладным знаниям и координации. Первый раздел формулирует концепцию и компонентный состав сервисного стека «Островок». Второй раздел разбирает работу DNS/DHCP-сервера dnsmasq без апстрима и при его исчезновении, unbound с копией корневой зоны и выбор зоны polygon.internal. Третий раздел анализирует технологии совместного хранения файлов (SMB, NFS, HTTP-каталог) и разграничение доступа. Четвёртый раздел описывает структуру и организацию офлайн-знаний: архивы ZIM, векторные тайлы OpenStreetMap и локальный полнотекстовый поиск. Пятый раздел охватывает средства версионирования (git bundle) и локальные текстовые коммуникации. Шестой раздел детально исследует физику ухода системных часов (RTC drift), последствия рассинхронизации для протоколов безопасности и настройку локального NTP-сервера chrony.
Архитектура автономного сервисного узла («Островок»)
Принципы проектирования изолированного стека
Попытка перенести стандартный корпоративный сервисный стек (тяжеловесные СУБД вроде PostgreSQL или Oracle, платформы совместной работы уровня GitLab, Nextcloud, почтовые серверы Microsoft Exchange или связки Matrix Synapse) на автономную машину в сценарии дефицита энергии не работает. Такие платформы рассчитаны на серверы с гигабайтами оперативной памяти и постоянным питанием: они держат в памяти СУБД и фоновые задачи, постоянно пишут на диск, расходуя ресурс флеш-памяти (TBW по теме 6), и не дают процессору уходить в простой 2. Точные цифры зависят от версии и нагрузки и здесь не приводятся; для своего узла их измеряют так же, как в ЛР 8.
Автономный узел «Островок» проектируется по принципиально иным критериям:
- Минимализм и низкое потребление ресурсов. Суммарный объем оперативной памяти, занимаемый всем комплексом базовых служб в состоянии покоя, должен измеряться десятками, а не сотнями мегабайт, а процессор в простое должен действительно простаивать — это требование проверяется измерением в ЛР 8 и позволяет платформе находиться в энергосберегающих состояниях C-states (тема 3) и сохранять ватт-часы аккумулятора.
- Восстановление и запуск одной командой. Сервисы не должны требовать ручного ввода паролей или сложных многостадийных процедур запуска. Конфигурации полностью версионируются в локальном репозитории (инфраструктура как код), а запуск осуществляется супервизором
systemdпо единой целиpolygon.target. - Отсутствие скрытых сетевых связей. Каждый сервис настраивается с жестким запретом на обращение к внешним сетевым интерфейсам, если они не объявлены явно. Попытка запросить внешний хост должна немедленно пресекаться локальным отказом без ожидания сетевого тайм-аута.
- Хранение в виде статических неизменяемых файлов. Вся база знаний, документация, карты и дистрибутивы хранятся в виде архивов только для чтения (ZIM, MBTiles, SquashFS), что исключает их логическое повреждение при внезапном обесточивании узла.
Компонентный состав и бюджет ресурсов
Стек «Островок» собран из небольших программ, каждая из которых делает одно дело и настраивается одним текстовым файлом 34. Таблица перечисляет их роли и причины выбора; потребление памяти не переписывается из чужих обзоров, а измеряется в ЛР 8 (./island.sh status читает VmRSS из /proc/<pid>/status).
| Сервис | Функция | Почему он, а не альтернатива |
|---|---|---|
dnsmasq | DNS для зоны polygon.internal, кэш, DHCP | BIND 9 избыточен для одной зоны; unbound — если нужен полноценный рекурсивный резолвер и DNSSEC |
chrony | сервер времени для сегмента, учёт ухода кварца (driftfile) | ntpd медленнее входит в синхронизацию после долгого перерыва и хуже работает с прерывистыми источниками |
nginx или python3 -m http.server | HTTP-каталог файлов и документации | Apache допустим, но тяжелее в настройке для одной задачи |
kiwix-serve | чтение ZIM-архивов без распаковки | локальная MediaWiki требует PHP и СУБД |
git daemon или SSH | репозитории конфигураций и документов | Gitea — один статический бинарный файл с SQLite, разумный выбор при нескольких пользователях; GitLab на узле с 2 ГБ ОЗУ не работает |
ngircd или Prosody | текстовый чат (IRC, XMPP) | Matrix Synapse тяжёл для одного узла |
Mumble (mumble-server) | голосовая связь в локальной сети | — |
recoll, rg | полнотекстовый поиск | Elasticsearch требует JVM и гигабайты памяти |
В опыте автора (ЛР 8, dnsmasq 2.90 в пространстве имён, Ubuntu 22.04) резидентная память dnsmasq с кэшем на 1000 записей составила 4608 КиБ (C-08-12: 4,5 МБ). Этого числа достаточно для главного вывода: службы имён, времени и файлов вместе занимают десятки мегабайт, и ограничение по памяти для узла из темы 6 создают не они, а kiwix-serve с большими архивами, поиск и графическая оболочка, если она есть. Полный бюджет своего узла записывается в журнал ЛР 8 по результатам ./island.sh status, а не по этой таблице.
Имена и адресация без внешнего апстрима
Локальный DNS: специфика работы без корневых серверов
В обычном режиме рекурсивный резолвер при получении запроса обращается к корневым серверам (тринадцать имён от a.root-servers.net до m.root-servers.net), затем к серверам зоны верхнего уровня и, наконец, к авторитетным серверам домена; устройство глобального DNS — курс «Компьютерные сети», тема 14 5.
В автономном узле корневые серверы недоступны. Если клиент настроен на резолвер, который пытается до них достучаться, то каждый запрос к имени, которого нет в кэше, ждёт тайм-аута: пакет уходит в никуда или отвергается с ICMP Network Unreachable, резолвер повторяет его к следующему серверу. Клиентская программа (curl, браузер, ssh) при этом блокируется на время, которое складывается из тайм-аутов резолвера и библиотеки клиента (options timeout и attempts в /etc/resolv.conf, по умолчанию 5 с и 2 попытки, см. resolv.conf(5)). Для пользователя это выглядит как «сеть тормозит», хотя локальные сервисы исправны.
Поэтому на узле работает dnsmasq — компактный DNS-сервер с кэшем и DHCP-сервер 35, настроенный так, чтобы ни один запрос не уходил наружу. Фрагмент conf/dnsmasq.conf из ЛР 8:
# Не читать /etc/resolv.conf и не пересылать запросы наружу
no-resolv
no-poll
# Имена без точки и обратные запросы для частных сетей наружу не уходят
domain-needed
bogus-priv
# Локальная зона: неизвестное имя в ней — сразу NXDOMAIN
local=/polygon.internal/
domain=polygon.internal
no-hosts
addn-hosts=/etc/polygon/hosts.internal
listen-address=10.88.0.1,127.0.0.1
bind-interfaces
cache-size=1000
Поведение такого сервера проверено опытом в ЛР 8 (dnsmasq 2.90) и выглядит так (C-08-03: REFUSED, код 5):
dig @10.88.0.1 wiki.polygon.internal → status: NOERROR, wiki.polygon.internal. A 10.88.0.1
dig @10.88.0.1 nosuch.polygon.internal → status: NXDOMAIN
dig @10.88.0.1 example.org → status: REFUSED
Имя из файла зоны получает ответ сразу. Неизвестное имя внутри polygon.internal получает NXDOMAIN — это и делает директива local=: зона объявлена своей, и dnsmasq не ищет её нигде больше. Внешнее имя при отсутствии апстрима получает не NXDOMAIN, а REFUSED: dnsmasq не утверждает, что имени нет, а сообщает, что спросить ему некого. Разница важна для диагностики: NXDOMAIN на внешнее имя означал бы, что кто-то выше по цепочке ответил, а REFUSED — что цепочка обрывается на узле. Клиент получает ответ сразу и не ждёт тайм-аута, что и требовалось.
Исчезновение апстрима и копия корневой зоны
В сценарии А связь есть, и узел пересылает внешние запросы провайдеру (строка server= вместо no-resolv). Что происходит, когда связь пропадает? Опыт ЛР 8 даёт два разных ответа. Имя, которое уже спрашивали, dnsmasq продолжает отдавать из кэша, пока не истёк срок жизни записи (TTL), — в опыте это 300 с, заданные «провайдером». Новое внешнее имя не получает никакого ответа: dnsmasq переслал запрос и ждёт, клиент получает тайм-аут. Это тот самый режим «сеть тормозит», от которого защищает конфигурация без апстрима, поэтому при переходе от сценария А к Б или В апстрим из конфигурации убирают явно, а не ждут, пока его отсутствие заметят пользователи.
Для узла, которому нужен настоящий рекурсивный резолвер (например, чтобы при восстановлении связи сразу работать без ручного переключения), используется unbound. Он умеет держать локальные зоны (local-zone: "polygon.internal." static) и хранить полную копию корневой зоны по RFC 8806 (auth-zone с name: ".") 13. Копия корня избавляет резолвер от обращений к корневым серверам: имена верхнего уровня (.org, .ru) он разрешает сам, а дальше всё равно нужен интернет. В автономии от этого две пользы: резолвер без задержек отвечает NXDOMAIN на несуществующие зоны верхнего уровня, а при появлении связи начинает работать без смены конфигурации. Файл root.zone скачивается заранее и устаревает: подписи DNSSEC в нём имеют срок действия порядка недель, и у устаревшей копии проверка подписей отказывает — это одна из причин, по которой точное время важно не только для Kerberos (раздел о времени).
Отдельный приём — split-horizon: одно и то же имя получает разные ответы в зависимости от того, откуда спросили. На узле с двумя сегментами (служебный и гостевой) files.polygon.internal для служебного сегмента указывает на файловый сервер, а для гостевого — на страницу с правилами; в unbound это делают access-control-view и view:, в dnsmasq — localise-queries для адресов, привязанных к интерфейсам.
Выбор локальной зоны: .internal, .home.arpa, .local
Выбор суффикса для локальных имён регламентирован 67:
.local(RFC 6762, Multicast DNS). Зона зарезервирована для mDNS (Avahi, Bonjour). Системные резолверы Linux (черезnss-mdns) и Apple отправляют запросhost.localгрупповой рассылкой на 224.0.0.251, порт 5353, минуя одноадресный DNS-сервер. Использовать.localв dnsmasq нельзя: часть клиентов его просто не спросит. По той же причине в ЛР 8 dnsmasq не ставят на порт 5353 — его занимает Avahi..home.arpa(RFC 8375). Зона для домашних сетей; не делегируется в глобальном DNS..internal. В 2024 году ICANN зарезервировала зону верхнего уровня.internalдля частного использования: она никогда не будет делегирована в корне, и её можно использовать в любой изолированной сети 14. Курс использует одну зону для всего Полигона —polygon.internal(wiki.polygon.internal,files.polygon.internal,time.polygon.internal).- Произвольные суффиксы (
.lan,.island,.corp) — плохой выбор: любой из них может быть делегирован в будущем, и тогда локальные имена начнут конфликтовать с глобальными.
Сетевая адресация: статика против DHCP
На автономном узле используется смешанная схема.
- Сам узел и инфраструктура получают адреса статически (в ЛР 8 —
10.88.0.1/24). Сервер не должен зависеть от собственного DHCP: если интерфейс узла ждёт адреса, а DHCP-сервер стартует только после поднятия интерфейса, загрузка зацикливается. - Клиенты получают адрес, шлюз, DNS и сервер времени по DHCP от того же dnsmasq (рисунок):
dhcp-range=10.88.0.100,10.88.0.150,255.255.255.0,1h
dhcp-option=option:router,10.88.0.1
dhcp-option=option:dns-server,10.88.0.1
dhcp-option=option:ntp-server,10.88.0.1
dhcp-authoritative
Конфликт адресов возникает, когда кто-то назначил себе статический адрес из пула DHCP. dnsmasq перед предложением адреса проверяет, не занят ли он: отправляет ICMP-эхо (ping) и, если ответ пришёл, помечает адрес как занятый и предлагает следующий (dnsmasq(8), опция --no-ping, которая эту проверку отключает). Цена проверки видна в ЛР 8: клиент получил адрес через 3,2 с после первого запроса (C-08-11: 3,2 с), почти всё это время — ожидание ответа на ping. Со стороны клиента защита дублируется: современные клиенты DHCP перед использованием адреса делают ARP-проверку (RFC 5227) и при конфликте отказываются от адреса сообщением DHCPDECLINE. Диагностика конфликта на узле — ip neigh (два MAC-адреса для одного IP в разное время) и журнал dnsmasq (log-dhcp).
Хранилища файлов и совместный доступ
Сравнение файловых протоколов в изоляции
Для организации обмена схемами, архивами пакетов, технической литературой и резервными копиями внутри замкнутого узла применяются три основных протокола файлового доступа 8:
| Протокол / Сервер | Плюсы в автономных условиях | Минусы и ограничения | Накладные расходы | Рекомендуемое назначение |
|---|---|---|---|---|
HTTP / WebDAV (nginx) | Максимальная простота; доступен из любого браузера; работает поверх любого плохого канала; нулевая настройка клиентов | WebDAV требует настройки в ОС для монтирования диска как блочного устройства | Минимальные | Основной архив знаний: раздача документации, ZIM, пакетов репозитория |
NFSv4 (nfs-kernel-server) | Нативная поддержка POSIX прав доступа, высокая производительность передачи файлов, работа в ядре Linux | Крайне слабая поддержка в Windows/Android; требует открытых портов RPC; чувствителен к обрывам линка | Умеренные (выполняется в ядре) | Монтирование сетевых томов между Unix-машинами и станциями восстановления |
SMB / CIFS (samba) | Нативная поддержка в Windows, macOS, Android и Linux; удобное разграничение доступа по паролям | Большой пакет и несколько демонов (smbd, nmbd), сложная настройка прав | Высокие | Общий файловый обмен с гетерогенными пользовательскими клиентами |
Простой HTTP-каталог: файлы без настроек
Наиболее устойчивым к сбоям форматом раздачи файлов в условиях автономии является статический HTTP-каталог с включённым автоиндексированием (англ. directory listing). Преимущество решения — абсолютная всеядность: файлы читаются любым встроенным веб-браузером, консольной утилитой curl или wget, не требуя установки специализированных сетевых клиентов.
Конфигурация виртуального хоста в nginx (/etc/nginx/sites-available/files.polygon.internal):
server {
listen 80;
server_name files.polygon.internal;
root /srv/storage/public;
# Включение красивого листинга каталогов
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
# Оптимизация отдачи больших статических файлов
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Ограничение скорости для предотвращения утилизации канала
limit_rate_after 10m;
limit_rate 2m;
}
Такая конфигурация позволяет клиентам загружать файлы со скоростью локального интерфейса без риска исчерпания сетевых буферов узла.
Разграничение прав и защита от исчерпания диска
В изолированной среде критически важно не допустить переполнения корневого раздела файлами пользователей. Если гостевой пользователь загрузит терабайтный архив на системный раздел, операционная система заблокируется из-за невозможности создать временные файлы /tmp и дескрипторы сокетов в /run.
Для нейтрализации риска применяются два архитектурных правила:
- Физическая или логическая изоляция хранилища. Каталог общего доступа
/srv/storageмонтируется на отдельный раздел или отдельный субтом Btrfs. - Дисковые квоты. На уровне файловой системы включаются жесткие лимиты на пользователя или группу (
btrfs qgroupилиedquota). - Разграничение прав POSIX. Доступ к модификации системных каталогов ограничен правами суперпользователя; общедоступная папка настраивается со специальным битом закрепления (англ. sticky bit), аналогично каталогу
/tmp:
Флагchmod 1777 /srv/storage/shared1(sticky bit) гарантирует, что удалить или переименовать файл может только его непосредственный создатель, даже если права записи открыты для всех участников группы.
Корзина, версии файлов и квоты
Пользователь в изоляции удаляет файл так же легко, как и в офисе, а восстановить его из облака нельзя. Поэтому общий каталог узла настраивается с защитой от ошибки человека в два слоя. Первый — корзина на сервере: в Samba модуль vfs_recycle переносит удалённые файлы в скрытый каталог .recycle вместо удаления; для HTTP-каталога то же даёт правило «загрузка в incoming/, перенос в основной каталог — только оператором». Второй — версии: снимки Btrfs по таймеру (тема 7) или ежечасная копия rsync --link-dest, где неизменённые файлы — жёсткие ссылки и места почти не занимают. Корзина и версии не заменяют резервную копию на другом носителе (тема 9): они защищают от ошибки, но не от отказа диска.
Квоты ограничивают, сколько места займёт один пользователь или каталог: btrfs qgroup limit 20G /srv/storage/shared для подтома Btrfs или setquota на ext4 с включённой опцией usrquota. Проверка квоты — часть ЛР 8 только в ветке ВМ: на файловых образах без root квоты не включаются.
Корпус офлайн-знаний и локальные справочники
Архивы ZIM и платформа Kiwix
Главная ценность автономного узла на уровне деградации 1 (тема 1) — доступ к справочным знаниям. Стандартный формат для сжатого хранения веб-контента — ZIM, развиваемый проектом openZIM и читаемый программами Kiwix 9:
- статьи, стили, шрифты и изображения упакованы в один файл; содержимое сжато кластерами (zstd в современных архивах, LZMA2 в старых), и для чтения одной статьи распаковывается только её кластер;
- внутри архива хранится полнотекстовый индекс Xapian, поэтому поиск работает без отдельной индексации;
- архив только читается, и порча при внезапном отключении питания ему не грозит.
kiwix-serve раздаёт архивы по HTTP; запросы он обрабатывает пулом потоков (--threads), так что «однопоточным» его считать неверно:
kiwix-serve --address 10.88.0.1 --port 8080 --threads 4 \
/srv/zim/wikipedia_ru_all_nopic.zim /srv/zim/wikimed_ru.zim
В ЛР 8 используется маленький архив zim/polygon_memo.zim (60 КиБ, три страницы памятки узла, исходники в zim/src/), собранный утилитой openZIM; на нём проверяется, что kiwix-serve запускается и отдаёт страницы и поиск, если пакет kiwix-tools установлен.
Размеры реальных архивов меняются с каждым выпуском, поэтому в тексте не приводятся: актуальный размер берётся из каталога https://download.kiwix.org/zim/ в день подготовки профиля (тема 2) и записывается в паспорт узла. Сам расчёт состава библиотеки под носитель варианта делается так. Пусть носитель имеет ёмкость C, под систему, конфигурации и резерв отводится доля r (в курсе 20 %), а архивы упорядочены по важности: 1 — медицина и безопасность, 2 — техника и ремонт, 3 — общая энциклопедия. Архивы добавляются по порядку, пока сумма их размеров S не превысит доступный объём:
S = \sum_{i=1}^{n} V_i \le C\,(1 - r)
На карту microSD 64 ГБ (59,6 ГиБ после разметки) при r = 0,2 помещается 59,6 · 0,8 = 47,7 ГиБ архивов. Если энциклопедия без иллюстраций не помещается вместе с остальным, правильное решение — взять её сокращённый вариант (_mini, _nopic), а не вычеркнуть медицину. Это правило «самое нужное — всегда, остальное — по требованию» из PLAN курса в виде арифметики; в ЛР 8 расчёт выполняется для носителя своего варианта по размерам из каталога.
Internet-in-a-Box — готовая сборка поверх Debian или Raspberry Pi OS, которая устанавливает Kiwix, офлайн-карты, Moodle, медиатеку и точку доступа Wi-Fi с одним веб-интерфейсом. Для учебного узла её полезно знать как образец состава, но собирать стек вручную по этой теме полезнее: готовая сборка скрывает именно те механизмы (DNS без апстрима, время, восстановление), ради которых тема написана, и обновляется по своему циклу.
Инженерная документация, man и DevDocs
Помимо общей энциклопедии, автономный узел должен содержать точную инженерную документацию по программным языкам и интерфейсам API 49:
- Системные руководства
manиtldr. В обычных дистрибутивах при минимизации пакетов документация часто вырезается. На автономном узле устанавливаются полные пакеты руководств:manpages,manpages-dev,manpages-posix-dev. Дополнительно развёртывается базаtldr— компактные примеры практических команд Linux с поиском по локальному кэшу. - Офлайн-каталог DevDocs. Платформа DevDocs объединяет документацию по десяткам языков и библиотек (Python, C/C++, Rust, Go, Bash, Linux Kernel, HTML/CSS). Локальный срез разворачивается в виде статического веб-приложения.
Офлайн-карты и векторные геоданные (OpenStreetMap)
В сценариях изоляции критически важна локальная навигация: топографическая обстановка, дороги, источники воды, высоты рельефа, линии электропередач.
Использование растровых скриншотов карт неэффективно: они занимают сотни гигабайт и теряют детализацию при масштабировании. Стандартом для автономных систем является хранение векторных тайлов OpenStreetMap в контейнерах формата MBTiles (база данных SQLite, содержащая векторные тайлы в формате Protocol Buffers) или PMTiles (однофайловый архив с возможностью считывания произвольных диапазонов байт) 10:
- Размер векторного архива области зависит от её площади и плотности застройки; его берут из каталога источника в день подготовки профиля и записывают в паспорт узла.
- Векторные тайлы содержат точные математические координаты всех объектов (дороги, дома, изолинии высот SRTM).
- Локальный тайловый сервер (например, компактный бинарник
mbtileserverна Go) раздаёт тайлы во встроенный веб-интерфейс на базе библиотеки Leaflet или OpenLayers. - Офлайн-маршрутизация выполняется встроенными движками (Valhalla или OSRM в режиме предварительно скомпилированного графа), позволяя строить оптимальные автомобильные и пешеходные маршруты полностью без интернета.
Для мобильных устройств группы удобнее не тайловый сервер, а готовые офлайн-приложения: OsmAnd и Organic Maps (Android, iOS) хранят векторные карты регионов OpenStreetMap и строят маршруты без сети. Файлы карт скачиваются заранее на машине с сетью и переносятся на устройства через файловый каталог узла; так телефон остаётся полезным навигатором даже без связи с узлом. Версии карт фиксируются в паспорте узла вместе с датой, потому что дорожная сеть меняется.
Полнотекстовый поиск: Recoll и Ripgrep
Для мгновенного поиска по сотням гигабайт накопленных PDF-книг, схем в формате DjVu, даташитов и исходных кодов используются два инструмента:
ripgrep(rg). Сверхбыстрый консольный инструмент построчного поиска, написанный на Rust. Оптимизирован под многопоточное сканирование директорий с аппаратным ускорением SIMD. Поиск по каталогу исходников или документации занимает доли секунды при данных в кэше страниц.recoll(Recoll Search Engine). Демон индексирования документов на базе движка Xapian. В фоновом режиме извлекает текстовые слои из файлов PDF, DOCX, ODT, HTML, ePub, DjVu и почтовых архивов mbox. При поиске ключевой фразы (например, «расчёт сечения провода DC-DC»)recollвыдаёт точные ссылки на страницы PDF-документов за доли секунды, избавляя инженера от необходимости вручную перебирать папки архива.
Координация, версионирование и локальная связь
Локальный Git и перенос через git bundle
В автономном узле распределённая система управления версиями Git служит не только для разработки программ, но и как центральный механизм управления инфраструктурой (конфигурации /etc/, сценарии восстановления, эксплуатационные журналы узла по теме 13) 411.
Для совместной работы нескольких специалистов внутри сегмента развёртывается локальный репозиторий Git. Для этого не требуются громоздкие веб-платформы: достаточно стандартного протокола SSH или легковесного демона git-daemon:
# Создание чистого общего репозитория на сервере
mkdir -p /srv/git/infrastructure.git
cd /srv/git/infrastructure.git
git init --bare --shared
# Разрешение анонимного чтения/клонирования через git-daemon (порт 9418)
touch git-daemon-export-ok
git daemon --reuseaddr --base-path=/srv/git --export-all --verbose &
Клиентские станции подключаются по локальному адресу:
git clone git://git.polygon.internal/infrastructure.git
Механизм git bundle для изолированного переноса коммитов
Когда узел находится в полной физической изоляции (Air-Gap) и прямого сетевого линка со внешней сборочной машиной нет, перенос изменений выполняется с помощью команды git bundle 11:
- На внешней машине, где были внесены изменения, создаётся монолитный бинарный контейнер («бандл»), содержащий всю цепочку коммитов, веток и тегов:
# Упаковка ветки main в единый переносимый файл git bundle create /media/usb/updates.bundle main - Бандл верифицируется утилитой
git bundle verify: контейнер включает в себя криптографические контрольные суммы объектов SHA-1/SHA-256. - На изолированном автономном узле бандл монтируется в карантинном режиме и импортируется напрямую в рабочий репозиторий:
# Извлечение коммитов из файла на сменном носителе git fetch /media/quarantine/updates.bundle main:incoming-updates # Слияние изменений git merge incoming-updates
Технология git bundle исключает передачу разрозненных diff-патчей, гарантируя целостность графа истории коммитов.
Локальные текстовые коммуникации: IRC против XMPP
Для координации операторов и фиксации оперативных журналов необходим текстовый чат, функционирующий внутри изолированной локальной сети без авторизации на внешних серверах:
- IRC (демон
ngircd). Простой выбор для минимального узла:ngircdнаписан на C, настраивается одним файлом и почти не требует ресурсов. Поддерживает разделение на каналы (#operations,#hardware,#alerts), сохранение истории и защиту каналов паролями. Клиенты для IRC существуют под любые операционные системы (от консольногоirssiдо графических HexChat и мобильных приложений). - XMPP (демон
prosody). Выбирается, если требуется передача графических изображений, файлов и поддержка сквозного шифрования OMEMO между мобильными клиентами (Conversations на Android). Prosody написан на Lua и настраивается на работу с внутренней доменной зонойchat.polygon.internal. - Голос: Mumble. Сервер
mumble-server(Murmur) даёт голосовую связь с низкой задержкой в локальной сети, клиенты есть для Linux, Windows и Android. Голос в изоляции нужен там, где текст медленен: координация работ в разных помещениях. Нагрузка на канал определяется кодеком Opus и задаётся в настройках сервера; для Wi-Fi одного сегмента она несущественна, для радиоканала темы 10 — запретительна.
Синхронизация времени и аппаратные часы
Физика ухода часов
Часы реального времени (англ. Real-Time Clock, RTC) материнской платы тактируются камертонным кварцевым резонатором на 32 768 Гц (2¹⁵ колебаний в секунду) и идут, пока узел выключен. Пока узел работает, время ведёт ядро по другому генератору, но и тот кварцевый, и рассуждение для него то же. Ошибку частоты принято выражать в миллионных долях (англ. parts per million, ppm): 1 ppm означает уход на 86 400 · 10⁻⁶ = 0,0864 с за сутки.
Паспортная начальная ошибка такого кварца при 25 °C — ±20 ppm. Это даёт уход 1,728 с за сутки (C-08-01: 1,728 с/сут; расчёт rtc.py), а в одиночку часы с такой ошибкой отойдут от истинного времени на 300 с за 173,6 суток. Важнее начальной ошибки температура. Частота камертонного кварца зависит от температуры по параболе с вершиной около 25 °C:
\frac{\Delta f}{f} = -k\,(T - T_0)^2,\qquad k = 0{,}034\ \text{ppm/°C}^2,\quad T_0 = 25\ ^\circ\text{C}
(C-08-08: 0,034 ppm/°C², даташит Abracon ABS07). Знак минус означает, что вдали от 25 °C кварц всегда отстаёт — и при холоде, и при жаре. Для сценария В (+5 °C в помещении без отопления, teacher/polygon.json) температурная добавка равна −0,034 · 20² = −13,6 ppm (C-08-04: −13,6 ppm), для сценария Б (−20 °C) — уже −68,9 ppm, втрое больше паспортной начальной ошибки.
Коррекция по driftfile
Демон chrony, пока у узла есть источник времени (сценарий А), измеряет ошибку частоты своих часов и записывает её в файл driftfile. После потери источника он продолжает вводить эту поправку. Начальная ошибка экземпляра (те самые ±20 ppm) и температура в момент калибровки при этом вычитаются; остаётся только изменение частоты после калибровки — в основном температурное. Расчёт rtc.py для сценария В при калибровке в тёплом помещении (+22 °C) и работе при +5 °C:
| Режим | Ошибка частоты | Сдвиг за 14 суток |
|---|---|---|
| без коррекции, худший случай (начальная ошибка + температура) | 33,6 ppm | 40,6 с |
после коррекции по driftfile | 13,4 ppm | 16,2 с |
(C-08-05: 16,2 с; C-08-06: 40,6 с). Коррекция убирает начальную ошибку, но не температуру. Отсюда практическое правило: калибровать часы (держать узел на связи с источником времени несколько часов) нужно в тех же условиях, в которых узел будет работать, а не на тёплом столе перед выездом. Для сценария Б разница ещё заметнее: при калибровке в +22 °C и работе в −20 °C остаточная ошибка 68,6 ppm, то есть 5,9 с за сутки.
Что на самом деле ломается от ухода часов
Распространённое утверждение «через полгода автономии Kerberos откажет» верно только для узлов, у которых нет общего источника времени. Протоколы делятся на две группы, и смешивать их нельзя 212.
Сравнивают часы двух сторон. Kerberos отвергает запрос, если часы клиента и KDC расходятся больше чем на clockskew — по умолчанию 300 с (C-08-09: 300 с; MIT Kerberos, krb5.conf(5), раздел [libdefaults]). TOTP (RFC 6238) делит время на шаги по 30 с, а проверяющая сторона рекомендованно принимает код соседнего шага (C-08-07: 30 с, ±1 шаг) 1. Для таких протоколов важно расхождение сторон, а не точность. Если клиенты и сервер берут время с одного локального NTP-сервера, их расхождение определяется сетевой задержкой (доли миллисекунды в одном сегменте) и не растёт, сколько бы ни уходил сам сервер от UTC. Порог становится угрозой для двух узлов без общего источника: при ошибках +20 и −20 ppm они расходятся со скоростью 40 ppm, и TOTP перестаёт гарантированно приниматься через 8,68 суток (C-08-10: 8,68 сут), а Kerberos отказывает через 86,8 суток (C-08-02: 86,8 сут). Отсюда вывод для сценария В: два узла Полигона, которые обмениваются данными (тема 11), должны синхронизироваться друг с другом — один служит сервером времени другому, — а не каждый сам по себе.
Сравнивают часы с абсолютным временем. Сроки сертификатов X.509 (notBefore, notAfter), сроки подписей DNSSEC (RRSIG), поле Valid-Until в файлах Release APT (тема 7), сроки ключей OpenPGP. Здесь общий локальный сервер не помогает: если он сам ушёл на сутки вперёд, все клиенты одинаково посчитают сертификат, истекающий сегодня, уже истёкшим. Порога «300 с» у TLS нет — граница определяется сроком конкретного сертификата, и ошибка становится заметной в момент, когда часы переходят эту дату. Практические следствия: сертификаты локальных сервисов выпускают с запасом по сроку (тема 7, «Локальные сертификаты и время»); резолвер с проверкой DNSSEC на узле с ушедшими часами начинает отвергать все подписанные ответы (поэтому в изоляции DNSSEC-проверку для локальной зоны не включают, а для копии корня — следят за временем); после долгой автономии часы узла сверяют с независимым источником до того, как принимать решения по срокам.
Наконец, журналы. При разборе отказа события с разных машин сопоставляются по меткам времени; если часы двух машин расходятся на минуты, порядок событий в сводном журнале может оказаться обратным действительному.
Автономный сервер времени chrony
Узел раздаёт время сегменту сам. В отсутствие внешних источников chrony по умолчанию считает свои часы несинхронизированными и клиентов не обслуживает; директива local разрешает это 12. Фрагмент conf/chrony-server.conf из ЛР 8:
local stratum 10 # раздавать своё время, объявив стратум 10
allow 10.88.0.0/24 # клиенты сегмента
driftfile /var/lib/chrony/chrony.drift
rtcsync # периодически переносить время ядра в RTC
Стратум 10 выбран намеренно большим: если в сети появится настоящий источник (GNSS-приёмник, связь с интернетом), клиенты предпочтут его. В ЛР 8 клиенты получают адрес сервера времени по DHCP (опция 42, ntp-server), а рассинхронизация проверяется опытом: часы сервера сдвигаются на 600 с (ISLAND_TIME_OFFSET=600, chronyd под libfaketime или запасной сервер offline_time.py), и клиент должен это обнаружить. Без прав root системные часы машины в опыте не меняются — клиент только измеряет смещение (chronyd -Q или offline_time.py query), и это осознанное ограничение опыта.
Аппаратные источники: DS3231 и GNSS
Термокомпенсированный генератор DS3231. Микросхема содержит датчик температуры и подстраивает частоту встроенного кварца; по даташиту (Analog Devices, бывш. Maxim) точность ±2 ppm в диапазоне 0…+40 °C и ±3,5 ppm в диапазоне −40…+85 °C (C-08-13: ±2 ppm). Для узла в неотапливаемом помещении правильная цифра — вторая. Модуль подключается по шине I²C: на одноплатных компьютерах — к выводам GPIO, на ноутбуке или ПК — через USB-адаптер I²C (например, на микросхеме CH341) и драйвер rtc-ds1307, который поддерживает DS3231. Простой USB-UART для этого не годится: у DS3231 нет последовательного порта. Если считать, что весь паспортный допуск ±2 ppm уже включает температуру (rtc.py --ppm 2 --k 0), одиночный узел уходит на 300 с за 1736 суток (C-08-14: 1736 сут); для сценария Б расчёт повторяют с 3,5 ppm.
GNSS-приёмник. Навигационный модуль даёт время по строкам NMEA через последовательный порт и импульс PPS раз в секунду. gpsd читает приёмник и передаёт время в chrony через общую память или сокет (refclock SHM 0 или refclock SOCK /run/chrony.ttyS0.sock, refclock PPS /dev/pps0). При видимости спутников это источник абсолютного времени, и с ним узел может раздавать время клиентам со стратумом 1. В 2026 году нужно учитывать, что сигнал GNSS может быть подавлен или подменён: приёмник без признаков подмены способен уверенно сообщить неверное время. Поэтому chrony настраивают так, чтобы GNSS не мог сдвинуть часы скачком (maxchange, refclock … maxlockage, сравнение с RTC узла), а расхождение больше заданного порога записывается в журнал для оператора, а не принимается молча.
Что эта модель не учитывает
- Нагрузку многих клиентов. Стек рассчитан на небольшую группу.
dnsmasqобслуживает запросы в одном процессе,kiwix-serve— пулом потоков (--threads); при десятках одновременных пользователей узким местом становятся диск с ZIM-архивами и процессор, а не сеть. Предел нагрузки своего узла измеряется, а не берётся из таблицы. - Суточные колебания температуры и старение кварца.
rtc.pyберёт одну постоянную температуру и линейное старение (±3 ppm за первый год по паспорту ABS07). При колебаниях температуры средний уход меньше худшего случая, но не равен нулю. - Достоверность GNSS. Модель считает GNSS источником абсолютного времени. При подавлении приёмник молчит (это видно), при подмене — сообщает неверное время (это не видно без сравнения с другим источником).
- Совместное редактирование в реальном времени. Git и статические ZIM рассчитаны на асинхронную работу; аналога совместного редактора документов в стеке нет.
- Повреждение баз при отключении питания. Индексы Recoll и базы SQLite при обесточивании во время записи могут потребовать восстановления по теме 9.
Итоги темы
Автономный сервисный узел превращает одну машину в инфраструктурный центр небольшой группы: имена, адреса, файлы, знания, координация и время работают без внешней сети. Принцип отбора сервисов — минимум программ, каждая настраивается одним текстовым файлом и восстанавливается одной командой; потребление ресурсов своего узла измеряется (ЛР 8), а не переписывается из таблиц.
Имена обслуживает dnsmasq в зоне polygon.internal; зона .internal зарезервирована ICANN и не будет делегирована. Без апстрима локальные имена получают ответ сразу, неизвестные имена в зоне — NXDOMAIN, внешние — REFUSED (C-08-03: REFUSED, код 5), и клиент не ждёт тайм-аутов. При пропаже апстрима кэш продолжает отдавать уже известные имена до истечения TTL, а новые внешние запросы зависают, поэтому апстрим из конфигурации убирают явно. unbound с копией корневой зоны и split-horizon — для узлов, где нужен полноценный резолвер. Клиенты получают адрес, DNS и сервер времени по DHCP; конфликт адресов dnsmasq ловит проверкой ping.
Знания хранятся в неизменяемых файлах: ZIM для энциклопедий и справочников, MBTiles и карты OsmAnd или Organic Maps для геоданных, man и tldr для документации; объём библиотеки подбирается под носитель варианта расчётом, а не наугад. Координация — Git с переносом через git bundle, текстовый чат и голос в локальной сети.
Время раздаёт chrony в режиме local stratum 10. Кварц с ошибкой 20 ppm уходит на 1,728 с за сутки (C-08-01: 1,728 с/сут), но главная составляющая ухода в холодном помещении — температура, и коррекция по driftfile её не убирает. Протоколы, сравнивающие часы двух сторон (Kerberos, TOTP), не страдают, пока все берут время с одного сервера; угроза для них — узлы без общего источника. От абсолютного времени зависят сроки сертификатов, подписей DNSSEC и файлов APT, и здесь помогает только независимый источник — DS3231 с известной погрешностью или GNSS, который нельзя принимать на веру.
Литература
- RFC 6238. TOTP: Time-Based One-Time Password Algorithm. — IETF, 2011. https://www.rfc-editor.org/rfc/rfc6238
- Anderson R. Security Engineering: A Guide to Building Dependable Distributed Systems. 3rd ed. — Wiley, 2020.
- Kelley S. dnsmasq: manual page. https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html
- Shotts W. The Linux Command Line: A Complete Introduction. 2nd ed. — No Starch Press, 2019.
- Albitz P., Liu C. DNS and BIND. 5th ed. — O'Reilly Media, 2006.
- RFC 6762. Multicast DNS. — IETF, 2013. https://www.rfc-editor.org/rfc/rfc6762
- RFC 8375. Special-Use Domain 'home.arpa.'. — IETF, 2018. https://www.rfc-editor.org/rfc/rfc8375
- Ward B. How Linux Works: What Every Superuser Should Know. 3rd ed. — No Starch Press, 2021.
- openZIM. ZIM file format. https://wiki.openzim.org/wiki/ZIM_file_format
- MBTiles Specification. https://github.com/mapbox/mbtiles-spec
- Chacon S., Straub B. Pro Git. 2nd ed. — Apress, 2014. https://git-scm.com/book/en/v2
- chrony documentation: chrony.conf(5). https://chrony-project.org/doc/4.6/chrony.conf.html
- RFC 8806. Running a Root Server Local to a Resolver. — IETF, 2020. https://www.rfc-editor.org/rfc/rfc8806
- ICANN. Approved Board Resolutions, Special Meeting of the ICANN Board, 29 July 2024: Reserving .INTERNAL for Private-Use Applications. https://www.icann.org/