Перейти к содержанию

Журнал работ

2026-07-27 — запись о работе, в ходе которой была построена эта платформа документации, изменена модель владения конфигурацией edge, закрыты эксплуатационные уязвимые места и вся документация переведена на семь языков.

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

Область охвата

Эксплуатационные детали — адреса хостов, пути, секретные значения настроек — в этот публичный сайт не входят; они находятся в закрытом runbook внутри соответствующего репозитория.

Пять направлений

# Работа Результат
1 Построить платформу документации docs.gerege.mn начал работать
2 Разделить владение конфигурацией edge Каждый домен переехал в свой репозиторий
3 Закрыть эксплуатационные уязвимые места Устранены случайное удаление и слепые зоны
4 Перевести документацию на семь языков Монгольский + шесть официальных языков ООН, полное покрытие
5 Отразить переход на Nexus Новая модель экосистемы задокументирована на семи языках

1. Платформа документации

Что сделано

Портал на MkDocs Material, собравший документацию уровня экосистемы Gerege в одном месте: 25 страниц, монгольский по умолчанию, диаграммы Mermaid, брендовый CSS. Языковое покрытие позже расширено до семи языков — см. раздел 4.

Содержимое собрано из README, каталогов docs/ и архитектурных документов репозиториев экосистемы и упорядочено в четыре части: слои · платформы · стандарты · эксплуатация.

Ключевые решения

Отдавать статический сайт из отдельного контейнера. Добавление нового mount в контейнер edge nginx требует его пересоздания — и в этот момент ненадолго падают все домены. При отдаче из собственного маленького контейнера на edge достаточно дополнить конфигурацию и выполнить reload.

Развёртывание на месте, через rsync. Каталог сайта пробрасывается в контейнер через bind mount. Если заменить каталог целиком (mv), контейнер продолжит смотреть на старый inode, и новое содержимое вообще не появится. rsync обновляет файлы на месте, поэтому mount остаётся действительным.

Якоря для кириллических заголовков. Штатный slugify расширения toc в Python-Markdown удаляет не-ASCII символы: заголовок ## Танилт получает пустой id, и внутристраничные ссылки молча ломаются. Перешли на сохраняющий unicode pymdownx.slugs.slugify.

Включили validation.anchors. В MkDocs проверка якорей по умолчанию выключена. Без неё битая ссылка на #раздел проходит сборку и попадает в production. Теперь она валит --strict.

Всё, что попало в docs/, становится публичным. Поэтому эксплуатационные детали размещены отдельно, в каталоге, не входящем в состав сайта.


2. Владение конфигурацией edge

Это было самое крупное архитектурное изменение.

Как было раньше

Все vhost всех доменов лежали в одном центральном файле. Следствие: чтобы поменять мелкую настройку для docs.gerege.mn, нужно было отправить PR в другой репозиторий и ждать деплоя другой команды. Владение размыто, изменения медленные.

Как стало

каталог конфигурации edge nginx
├── (общие файлы)             ← принадлежат репозиторию объединённого стека
│     sso · dan · gsign · xyp
├── docs.gerege.mn.conf       ← docs-gerege-mn
├── developer.gerege.mn.conf  ← developer-gerege-mn
└── template.gerege.mn.conf   ← template-gerege-mn

Теперь vhost каждого домена живёт в репозитории соответствующего сервиса, и его устанавливает собственный деплой этого сервиса. Изменение завершается внутри одного репозитория.

Почему это работает

Каталог конфигурации физически находится внутри рабочей копии другого репозитория, а деплой того репозитория выполняет git reset --hard. git reset --hard восстанавливает только tracked-файлы — untracked он не трогает. Поэтому файл, установленный из сторонного репозитория, выживает.

Три условия полной независимости

Vhost не должен зависеть от общих файлов ни в чём:

Условие Почему
Собственный limit_req_zone Обращение к общему файлу зон создаёт зависимость от него
Собственный блок listen 80 (ACME + redirect) Тогда обновление сертификата работает самостоятельно
Переустановка при каждом деплое Если файл потеряется, он восстановится сам

Порядок миграции без простоя

  1. Сначала установить новый файл. В этот момент один домен определён в двух местах, но nginx обрабатывает это лишь как предупреждение conflicting server name — поведение не меняется, оба указывают на один upstream.
  2. Затем убрать его из центрального файла. Дублирование исчезает, новый файл вступает в силу.

Если сделать наоборот, между двумя шагами домен упадёт.

Никаких путей хоста в открытом репозитории

template-gerege-mn — с открытым исходным кодом, а действующее соглашение предписывало хранить данные о сервере только в secrets CI. Поэтому установщик сам определяет путь к каталогу конфигурации по mount'ам контейнера edge:

docker inspect <edge> --format \
  '{{range .Mounts}}{{if eq .Destination "/etc/nginx/conf.d"}}{{.Source}}{{end}}{{end}}'

Это надёжнее, чем прописывать путь жёстко, поэтому впоследствии применено во всех трёх репозиториях — оно продолжает работать при смене хоста или пути.

Предварительное условие по безопасности

СНАЧАЛА сертификат, потом vhost. Если добавить HTTPS-vhost, когда сертификата ещё нет, nginx -t упадёт, и в этот момент все домены под угрозой. ACME challenge работает через общий default server на порту 80, поэтому для получения сертификата менять конфигурацию не нужно.

Перед push итоговая конфигурация была проверена командой nginx -t во временном контейнере — с реальной сетью и сертификатами.


3. Эксплуатационное укрепление

Защита от случайного удаления

Файлы, на которых держится production, не были tracked ни в одном репозитории. git reset --hard их не трогает, но git clean -fd удаляет.

Файл Если потерять Решение
Vhost трёх доменов 3 домена падают одновременно .gitignore
Compose override хоста Контейнер отпадает от сети edge → 502 .gitignore + файл-пример

Почему .gitignore это решает: без -x git clean пропускает игнорируемые файлы. Это защищает их, не требуя делать их tracked.

Compose override нельзя делать tracked: compose читает его автоматически, поэтому production-настройки принудительно применились бы в локальной среде каждого разработчика. Поэтому живой файл остаётся untracked на хосте, а в репозитории хранится только пример для восстановления. Что пример даёт тот же результат, что живая конфигурация, подтвердили сравнением вывода docker compose config.

Мониторинг работоспособности

Скрипт мониторинга на хосте существовал и раньше, но совпали два дефекта: он вообще не был зарегистрирован в cron, и часть указанных в нём контейнеров переименовали, так что они пропали. Отсутствующий контейнер скрипт молча пропускает, поэтому о полной остановке мониторинга никто не знал.

Ключевые решения в новой версии:

Собственный healthcheck контейнера — первым. Его interval и retries уже подогнаны под конкретный сервис.

HTTP-probe выполняется изнутри контейнера. Если проверять всё через edge, то при падении edge все сервисы выглядели бы упавшими, что вызвало бы массовый restart и скрыло настоящий сбой. Теперь каждая проверка самостоятельна.

Порог последовательных ошибок + cooldown. Кратковременная задержка (деплой, GC, нагрузка) не вызывает restart; сломанный сервис не гасится и не поднимается по кругу.

Stateful-инфраструктуру автоматически не перезапускаем. Базы данных и кэш только контролируются и записываются. Restart не устраняет реальную причину вроде заполненного диска, зато рвёт транзакции многих стеков и только добавляет ущерб. В таком случае решает человек.

Отсутствующий контейнер записывается как ошибка — чтобы не повторять главный дефект прежней версии.

Порог · cooldown · политика по stateful · восстановление · отсутствующий контейнер — все пять моделей поведения проверены по-настоящему на изолированном тестовом контейнере.

Обновление сертификатов

Увидеть запись в cron недостаточно: то, действительно ли работает обновление, проверили через --dry-run и подтвердили, что все домены обновляются успешно. Это риск того типа, что молчит вплоть до истечения срока.


4. Покрытие семью языками

Что сделано

Все страницы сайта переведены на монгольский плюс шесть официальных языков ООН: العربية · 中文 · English · Français · Русский · Español. Ранее английский перевод был частичным (главная, введение, слои, список платформ, аутентификация); его довели до полного и добавили ещё пять языков.

Ключевые решения

Монгольский остаётся источником. Остальные шесть — переводы: оригинал пишется по-монгольски и переводится оттуда. При двух «исходных» языках содержимое начинает молча расходиться.

Переводим постранично, а не по языкам. Когда один документ переводится на шесть языков одновременно, терминология, строки таблиц и структура остаются одинаковыми. При обратном подходе — «сначала все страницы по-английски» — языки, переведённые позже, будут догонять уже изменившийся исходный текст.

Техническую запись не переводили. Домены, имена репозиториев, код, YAML, пути endpoint'ов, названия стандартов (OIDC · PKCE · RFC 3161) остаются во всех языках как есть. При переводе их стало бы невозможно скопировать и выполнить.

Имена файлов не переводятся. Это platforms/sso.ru.md, а не платформы/sso.md. Благодаря этому путь в URL при смене языка не меняется, и глубокие ссылки, приходящие из других репозиториев, продолжают работать.

Арабский RTL не делали вручную. Material распознаёт локаль ar, проставляет <html dir="rtl"> и сам разворачивает меню и поток содержимого. Блоки кода и ASCII-диаграммы остаются LTR — и это правильно, потому что команды и URL при смене направления теряют смысл.

Unicode-slugify стал втрое важнее. Введённый ради кириллических заголовков pymdownx.slugs.slugify теперь держит и якоря арабских, китайских и русских заголовков. Со стандартным slugify молча сломались бы все внутристраничные ссылки в шести локалях.

fallback_to_default остаётся включённым. Сейчас все страницы переведены, поэтому fallback не срабатывает, — но он остаётся гарантией того, что сайт останется целым, когда добавится новая страница, а её перевод отстанет.

Область применения

Эта политика касается документации уровня экосистемы, то есть только этого сайта. Глубокая техническая документация конкретной платформы (схемы endpoint'ов, справочник SDK) остаётся MN + EN внутри своего репозитория: её читатель — инженер, уже работающий с этим репозиторием, поэтому расширять охват малополезно.


5. Отражение перехода на Nexus (2026-08-07)

Что произошло

Репозиторий open-gerege-nexus создан 2026-08-05, а 08-07 платформа была переименована в Gerege Nexus и переехала на nexus.gerege.mn. Затем появились два форка: sso-gerege-nexus (Gerege SSO) и eduge-mn-nexus (eduge.mn). Поскольку это меняет модель распространения экосистемы, документация была выровнена на всех семи языках.

Ключевые решения

Ни одна страница существующих платформ не удалена. Template, Gerege Platform, SSO и Kiosk по-прежнему в production. Добавлена новая страница, а на прежних поставлено предупреждение о том, какое состояние действует. Удаление страницы стёрло бы документацию работающей системы.

Слой 3 разделён на два поколения. Nexus не заменяет Template — оба находятся на слое 3 одновременно. Введение нового слоя обесценило бы само правило слоёв («не обращаться через слой»).

Собственный OIDC-провайдер Nexus — это НЕ слой 2. В Nexus есть провайдер OAuth2/OIDC, но он обслуживает арендаторов и сторонних клиентов именно этого развёртывания. Путь экосистемы к идентификации гражданина остаётся Gerege SSO. Без такой оговорки читатель заключил бы, что «Nexus заменил SSO».

Каждый домен проверен вручную. Работоспособность nexus.gerege.mn, eduge.mn и geregekiosk.mn подтверждена по DNS, HTTP и TLS-сертификатам. Это выявило две вещи: на geregekiosk.mn теперь работает Nexus, а open.gerege.mn исключён из сертификата того хоста, поэтому HTTPS падает на несовпадении имени.

Исправлено устаревшее обещание. На странице Template утверждалось, что autosync ежедневно доставляет изменения вниз по потоку; эта автоматика была остановлена по всему флоту 2026-08-06. Ложное обещание хуже отсутствующего факта: инженер будет ждать, что исправление доедет само.

Закрыты два пробела в переводах. Раздел «Самостоятельные брендовые домены» в карте доменов оказался полностью отсутствующим во всех шести переводах; теперь все семь выровнены.

Сводка решений

Решение Обоснование
Статический сайт в отдельном контейнере Пересоздание edge валит все домены
rsync, а не mv Bind mount остаётся на старом inode
Unicode slugify Стандартный slugify уничтожает якоря кириллических заголовков
Включить validation.anchors Иначе битые ссылки попадают в production
Vhost — в репозитории сервиса Изменение завершается внутри одного репозитория
Полностью самодостаточный vhost Зависимость от общего файла лишает разделение смысла
Сначала установить, потом убрать При обратном порядке домен падает
Определять путь автоматически Надёжнее жёсткой записи; не оставляет путь в открытом репозитории
Защита через .gitignore git clean пропускает игнорируемые файлы
Не делать override tracked Compose читает его автоматически — production-настройки применятся локально
Probe изнутри контейнера Предотвращает массовый restart при падении edge
Stateful не перезапускать Restart не устраняет причину, а добавляет ущерб
Монгольский — единственный исходный язык При двух «источниках» содержимое молча расходится
Переводить постранично, а не по языкам Терминология и структура остаются одинаковыми в шести языках
Не переводить код, домены и имена репозиториев После перевода их нельзя скопировать и выполнить
Не переводить имена файлов Путь в URL одинаков при смене языка, глубокие ссылки работают
Каждый язык на подпути (/ar/) С поддоменами разом вырастут SAN · vhost · hreflang

Сознательно не сделано

Домены sso · dan · gsign · xyp не выделяли. Их код лежит внутри репозитория объединённого стека, так что центральный файл и есть их собственный репозиторий. Причин выделять нет.

Отдельный mount в контейнер edge не добавляли. Это потребовало бы правки compose-файла объединённого стека и вдобавок пересоздания контейнера, что ненадолго уронило бы все домены.

Монгольский индекс поиска не форсировали. lunr.js не поддерживает монгольский, поэтому поиск в локали по умолчанию работает на стандартной токенизации. Написание отдельного stemmer'а стоит дороже, чем даёт сейчас.

Отдельный домен или поддомен для каждого языка не выделяли. Пути вида /ar/ и /zh/ решаются одним сертификатом, одним vhost и одним деплоем. Переход на поддомены разом увеличил бы SAN сертификата, конфигурацию edge и hreflang.

Остаточный риск

Vhost трёх доменов и файлы override на хосте защищены .gitignore, но если кто-нибудь запустит git clean -fdx (который затрагивает и игнорируемые файлы), они будут удалены. Способ исправления — заново запустить скрипт установки соответствующего репозитория; это отмечено в runbook каждого из трёх репозиториев.