fix: cyrillic fonts, dev certs, page head, and ru-text typography (#67)

🤖 PR generated for branch `fix/cyrillic-fonts-dev-certs-and-page-head`.

### Changes Summary
- **Fonts**: Add Cyrillic glyph subsets for Lato, EB Garamond, and Fira
Code.
- **Dev**: Support remote Root CA and encrypted dev CA key in
`.env.dev`.
- **UI & Kits**: Add size variant to `Breadcrumbs` component and enlarge
`PageHead` breadcrumbs with tuned collapse scroll threshold.
- **ESLint**: Add `no-hanging-particles` i18n & markdown typography rule
with auto-fixer.
- **Content & i18n**: Apply `ru-text` editorial and typography polish
(em dashes, number ranges, «ё» restoration, non-breaking spaces) across
`services/www` and `services/docs`.
This commit is contained in:
deytenit authored and GitHub committed 2026-10-25 15:21:09 +03:00
1 parent c6487dc258
commit b3638f4d63
111 files changed
+2350 -1512

No files matched your search

@@ -1,10 +1,10 @@
---
title: Руководства
description: Практические руководства по сборке репозиториев узлов, разработке действий и настройке композиций.
description: Практические руководства по сборке репозиториев узлов, разработке действий и настройке композиций.
weight: 20
---
Практические руководства для повседневной работы с apex. Каждое руководство решает одну прикладную задачу: сборку репозитория [узла](/apex/glossary#node), разработку [действий](/apex/glossary#action) для [движка](/apex/glossary#engine), определение [композиций](/apex/glossary#compositions) сервисов и эксплуатацию базового хоста.
Практические руководства для повседневной работы с apex. Каждое руководство решает одну прикладную задачу: сборку репозитория [узла](/apex/glossary#node), разработку [действий](/apex/glossary#action) для [движка](/apex/glossary#engine), определение [композиций](/apex/glossary#compositions) сервисов и эксплуатацию базового хоста.
## Доступные руководства {#available-guides}
@@ -17,7 +17,7 @@ weight: 20
---
**См. также:**
**См. также:**
- [Docs: Getting started](/apex/getting-started)
- [Docs: Concepts](/apex/concepts)
@@ -1,10 +1,10 @@
---
title: Разработка действий
description: Создавайте, переопределяйте и тестируйте действия движка в commons или proprietaries.
description: Создавайте, переопределяйте и тестируйте действия движка в commons или proprietaries.
weight: 22
---
**Контекст:** вся автоматизация устроена в виде [действий](/apex/glossary#action) — по одному Python-модулю на каждое действие, диспетчеризуемых по пути к файлу. Общий код находится в каталоге [commons](/apex/glossary#commons) `actions/` («commons» — общий репозиторий), а специфика конкретного узла — в его `proprietaries/actions/`. Оба дерева используют единый контракт, а запускатель добавляет оба каталога в `sys.path`. Поэтому локальное действие импортирует `engine.*` точно так же, как и действие из commons.
**Контекст:** вся автоматизация устроена в виде [действий](/apex/glossary#action) — по одному Python-модулю на каждое действие, диспетчеризуемых по пути к файлу. Общий код находится в каталоге [commons](/apex/glossary#commons) `actions/` («commons» — общий репозиторий), а специфика конкретного узла — в его `proprietaries/actions/`. Оба дерева используют единый контракт, а запускатель добавляет оба каталога в `sys.path`. Поэтому локальное действие импортирует `engine.*` точно так же, как и действие из commons.
## Минимальное действие {#minimal}
@@ -29,14 +29,14 @@ def run(ctx, args):
ctx.log.success("Конфигурация развёрнута.")
```
Путь к файлу задаёт имя действия: модуль выше вызывается как `apex utils/example`. Верните `0` или `None` при успехе, целое число — для конкретного кода выхода, либо выбросьте `SystemExit(code)`. [Соглашение о кодах выхода](/apex/concepts/actions-and-overlay#exit-codes): `64` — ошибка использования, `65` — ошибка данных, `66` — отсутствие входных данных, `1` — общая ошибка.
Путь к файлу задаёт имя действия: модуль выше вызывается как `apex utils/example`. Верните `0` или `None` при успехе, целое число — для конкретного кода выхода, либо выбросьте `SystemExit(code)`. [Соглашение о кодах выхода](/apex/concepts/actions-and-overlay#exit-codes): `64` — ошибка использования, `65` — ошибка данных, `66` — отсутствие входных данных, `1` — общая ошибка.
## Соглашения для фоновых действий {#unattended}
## Соглашения для фоновых действий {#unattended}
Действия, которые запускаются по cron без надзора оператора, следуют двум правилам, чтобы сбои отправляли оповещение, а не исчезали бесследно:
Действия, которые запускаются по cron без надзора оператора, следуют двум правилам, чтобы сбои отправляли оповещение, а не исчезали бесследно:
- **Оберните тело** в `try/except Exception` и отправьте `ctx.notify.error(...)` перед тем как выбросить `SystemExit(1)` — это Python-аналог перехвата `ERR` в shell.
- **Привяжите код выхода к итоговому уведомлению**: методы `ctx.notify.*` возвращают `False`, если доставить сообщение не удалось. Запуск, чьё статусное сообщение не дошло, всё равно должен завершиться с кодом 1.
- **Оберните тело** в `try/except Exception` и отправьте `ctx.notify.error(...)` перед тем как выбросить `SystemExit(1)` — это Python-аналог перехвата `ERR` в shell.
- **Привяжите код выхода к итоговому уведомлению**: методы `ctx.notify.*` возвращают `False`, если доставить сообщение не удалось. Запуск, чьё статусное сообщение не дошло, всё равно должен завершиться с кодом 1.
```python
try:
@@ -54,7 +54,7 @@ def run(ctx, args):
## Переопределение, обёртывание, отключение {#override}
Проприетарный файл с тем же относительным путём, что и действие из commons, полностью перекрывает его. Внутри переопределяющего модуля вызов `ctx.commons.run(args)` обращается к исходной реализации из commons — по паттерну обёртки:
Проприетарный файл с тем же относительным путём, что и действие из commons, полностью перекрывает его. Внутри переопределяющего модуля вызов `ctx.commons.run(args)` обращается к исходной реализации из commons — по паттерну обёртки:
```python {filename="proprietaries/actions/sync/packages.py"}
from engine.descriptor import Meta, Arg
@@ -67,23 +67,23 @@ def run(ctx, args):
return ctx.commons.run(args) # делегирование реализации из commons
```
Чтобы отключить действие на конкретном узле, укажите `DISABLED = "reason"` на верхнем уровне модуля. Диспетчер отклонит запуск такого действия с кодом выхода 1, а в общем списке появится отметка `[DISABLED: reason]`.
Чтобы отключить действие на конкретном узле, укажите `DISABLED = "reason"` на верхнем уровне модуля. Диспетчер отклонит запуск такого действия с кодом выхода 1, а в общем списке появится отметка `[DISABLED: reason]`.
## Тестирование {#testing}
Набор тестов commons построен на стандартном модуле `unittest`:
Набор тестов commons построен на стандартном модуле `unittest`:
```bash
./tests/run.sh # python3 -m unittest discover -s tests -p 'test_*.py' -v
```
Изменения в&nbsp;движке должны сохранять тесты зелёными. Быстрая smoke-проверка для&nbsp;любого действия (как из&nbsp;commons, так и&nbsp;проприетарного)&nbsp;— запуск со&nbsp;справкой: команда `apex <name> --help` должна завершаться с&nbsp;кодом&nbsp;0. Это подтверждает, что модуль синтаксически корректен, объект `METADATA` сформирован без&nbsp;ошибок, а&nbsp;механизм обнаружения находит действие.
Изменения в движке должны сохранять тесты зелёными. Быстрая smoke-проверка для любого действия (как из commons, так и проприетарного) — запуск со справкой: команда `apex <name> --help` должна завершаться с кодом 0. Это подтверждает, что модуль синтаксически корректен, объект `METADATA` сформирован без ошибок, а механизм обнаружения находит действие.
Хук pre-commit в&nbsp;commons (скрипт `init.sh` подключает его через `core.hooksPath`) запускает `apex utils/lint-docker-compose --hook` для&nbsp;подготовленных (staged) compose-файлов, поэтому ошибки в&nbsp;композициях отлавливаются ещё на&nbsp;этапе коммита.
Хук pre-commit в commons (скрипт `init.sh` подключает его через `core.hooksPath`) запускает `apex utils/lint-docker-compose --hook` для подготовленных (staged) compose-файлов, поэтому ошибки в композициях отлавливаются ещё на этапе коммита.
---
**См.&nbsp;также:**
**См. также:**
- [Concept: Actions and the overlay](/apex/concepts/actions-and-overlay)
- [Concept: The execution context](/apex/concepts/execution-context)
@@ -1,25 +1,25 @@
---
title: Эксплуатация узла
description: "Операции day-2: конфигурирование, композиции, резервные копии и синхронизация репозитория."
description: "Операции day-2: конфигурирование, композиции, резервные копии и синхронизация репозитория."
weight: 24
---
**Контекст:** после первоначальной настройки [узел](/apex/glossary#node) управляется исключительно через [движок](/apex/glossary#engine). В&nbsp;этом руководстве описаны регулярные операции и&nbsp;их фактические действия на&nbsp;хосте.
**Контекст:** после первоначальной настройки [узел](/apex/glossary#node) управляется исключительно через [движок](/apex/glossary#engine). В этом руководстве описаны регулярные операции и их фактические действия на хосте.
## Конфигурирование хоста {#configure}
Команда `apex configure` выполняет все шаги настройки в&nbsp;строгой последовательности. Если изменился только один слой, нужный шаг можно вызвать отдельно:
Команда `apex configure` выполняет все шаги настройки в строгой последовательности. Если изменился только один слой, нужный шаг можно вызвать отдельно:
| Шаг | Эффект |
|---|---|
| `configure/base` | Готовит базовую систему Debian: обновляет списки пакетов, ставит базовые утилиты (`curl`, `wget`, `git`, `rsyslog`), запускает службу rsyslog, устанавливает `ufw` и&nbsp;вспомогательный скрипт `ufw-docker` в&nbsp;`~/.local/bin/ufw-docker`. |
| `configure/ufw` | Применяет правила хоста, настраивает цепочки правил ядра и&nbsp;пробрасывает правила для&nbsp;контейнеров через `ufw-docker`. Поддерживает флаг `--dry-run`. |
| `configure/crowdsec` | Устанавливает CrowdSec (если он ещё не&nbsp;поставлен), копирует `configs/crowdsec/` в&nbsp;`/etc/crowdsec/`, ставит коллекции и&nbsp;firewall bouncer (если включён в&nbsp;настройках). Поддерживает флаг `--dry-run`. |
| `configure/cron` | Рендерит `configs/cron/crontab` через шаблонизатор и&nbsp;устанавливает расписание. Предусмотрены предпросмотр, аварийная остановка при&nbsp;пустом результате (код выхода&nbsp;65) и&nbsp;откат при&nbsp;ошибке установки. Поддерживает флаг `--dry-run`. |
| `configure/systemd` | Рендерит файлы первого уровня из&nbsp;`configs/systemd/` в&nbsp;`/etc/systemd/system/`, перезагружает конфигурацию systemd (daemon-reload) и&nbsp;активирует каждый юнит `.service`. Поддерживает флаг `--dry-run`. |
| `configure/routing` | Включает пересылку IP-пакетов (forwarding), задаёт правило и&nbsp;таблицу маршрутизации (policy-routing) по&nbsp;метке fwmark для&nbsp;границы анклава. Шаг идемпотентен. |
| `configure/base` | Готовит базовую систему Debian: обновляет списки пакетов, ставит базовые утилиты (`curl`, `wget`, `git`, `rsyslog`), запускает службу rsyslog, устанавливает `ufw` и вспомогательный скрипт `ufw-docker` в `~/.local/bin/ufw-docker`. |
| `configure/ufw` | Применяет правила хоста, настраивает цепочки правил ядра и пробрасывает правила для контейнеров через `ufw-docker`. Поддерживает флаг `--dry-run`. |
| `configure/crowdsec` | Устанавливает CrowdSec (если он ещё не поставлен), копирует `configs/crowdsec/` в `/etc/crowdsec/`, ставит коллекции и firewall bouncer (если включён в настройках). Поддерживает флаг `--dry-run`. |
| `configure/cron` | Рендерит `configs/cron/crontab` через шаблонизатор и устанавливает расписание. Предусмотрены предпросмотр, аварийная остановка при пустом результате (код выхода 65) и откат при ошибке установки. Поддерживает флаг `--dry-run`. |
| `configure/systemd` | Рендерит файлы первого уровня из `configs/systemd/` в `/etc/systemd/system/`, перезагружает конфигурацию systemd (daemon-reload) и активирует каждый юнит `.service`. Поддерживает флаг `--dry-run`. |
| `configure/routing` | Включает пересылку IP-пакетов (forwarding), задаёт правило и таблицу маршрутизации (policy-routing) по метке fwmark для границы анклава. Шаг идемпотентен. |
После `configure/ufw` обязательно перезапустите службу: `sudo systemctl restart ufw`. Действие выведет напоминание и&nbsp;проверит, что порт SSH остаётся открытым.
После `configure/ufw` обязательно перезапустите службу: `sudo systemctl restart ufw`. Действие выведет напоминание и проверит, что порт SSH остаётся открытым.
## Жизненный цикл композиции {#compose}
@@ -30,7 +30,7 @@ apex compose restart
apex compose up --dry-run # только напечатать команды
```
Все дополнительные аргументы движок передаёт напрямую в&nbsp;`docker compose` каждого проекта. Файлы `.env` и&nbsp;`apex.env` проектных каталогов подставляются автоматически при&nbsp;их наличии.
Все дополнительные аргументы движок передаёт напрямую в `docker compose` каждого проекта. Файлы `.env` и `apex.env` проектных каталогов подставляются автоматически при их наличии.
## Резервные копии {#backups}
@@ -38,14 +38,14 @@ apex compose up --dry-run # только напечатать команды
apex backup/run "$(cat <path-to-telegram-url-file>)"
```
Команда запускает [resticontainer](https://github.com/deytenit/resticontainer) в&nbsp;режиме one-shot под&nbsp;профилем compose `manual`. Процесс проходит следующие этапы:
Команда запускает [resticontainer](https://github.com/deytenit/resticontainer) в режиме one-shot под профилем compose `manual`. Процесс проходит следующие этапы:
1. Инициализирует репозиторий (если он ещё не&nbsp;создан).
2. Считывает compose-метки `restic.*` (`restic.enable`, `restic.backup.paths` и&nbsp;т.&nbsp;д.), чтобы определить, какие сервисы и&nbsp;пути нужно сохранять.
3. Создаёт единый снимок restic для&nbsp;всех выбранных путей хоста (`--host`&nbsp;= `APEX_NODE_HOST`, `--compression max`).
4. Применяет политику хранения (`forget --prune`) и&nbsp;отправляет уведомление.
1. Инициализирует репозиторий (если он ещё не создан).
2. Считывает compose-метки `restic.*` (`restic.enable`, `restic.backup.paths` и т. д.), чтобы определить, какие сервисы и пути нужно сохранять.
3. Создаёт единый снимок restic для всех выбранных путей хоста (`--host` = `APEX_NODE_HOST`, `--compression max`).
4. Применяет политику хранения (`forget --prune`) и отправляет уведомление.
Если ни&nbsp;один сервис с&nbsp;меткой `restic.*` не&nbsp;сформировал нового снимка, действие завершается с&nbsp;ошибкой. Полный процесс описан в&nbsp;разделе [Concept: Tiered storage](/apex/concepts/tiered-storage#backups). Чтобы резервное копирование и&nbsp;отправка отчётов в&nbsp;Telegram выполнялись автономно, добавьте команду в&nbsp;`configs/cron/crontab`.
Если ни один сервис с меткой `restic.*` не сформировал нового снимка, действие завершается с ошибкой. Полный процесс описан в разделе [Concept: Tiered storage](/apex/concepts/tiered-storage#backups). Чтобы резервное копирование и отправка отчётов в Telegram выполнялись автономно, добавьте команду в `configs/cron/crontab`.
## Синхронизация репозитория (capture-up) {#sync}
@@ -53,9 +53,9 @@ apex backup/run "$(cat <path-to-telegram-url-file>)"
apex sync/repository <telegram-bot-url>
```
Действие фиксирует изменения рабочей копии в&nbsp;ветке `sync/<node>`, выполняет rebase на&nbsp;`origin/main` и&nbsp;отправляет ветку через force-push с&nbsp;lease. Затем CI автоматически вливает изменения обратно в&nbsp;`main`. Этот механизм (capture-up) сохраняет актуальное состояние узла в&nbsp;репозиторий.
Действие фиксирует изменения рабочей копии в ветке `sync/<node>`, выполняет rebase на `origin/main` и отправляет ветку через force-push с lease. Затем CI автоматически вливает изменения обратно в `main`. Этот механизм (capture-up) сохраняет актуальное состояние узла в репозиторий.
Действие прерывается с&nbsp;подробным сообщением, если подмодуль commons содержит незакоммиченные изменения или каталог `compositions/` отсутствует. При&nbsp;конфликтах rebase выполнение останавливается для&nbsp;ручного вмешательства. Подробнее архитектурная модель описана в&nbsp;разделе [Concept: Framework architecture](/apex/concepts/architecture#capture-up).
Действие прерывается с подробным сообщением, если подмодуль commons содержит незакоммиченные изменения или каталог `compositions/` отсутствует. При конфликтах rebase выполнение останавливается для ручного вмешательства. Подробнее архитектурная модель описана в разделе [Concept: Framework architecture](/apex/concepts/architecture#capture-up).
## Мониторинг обновлений {#updates}
@@ -63,7 +63,7 @@ apex sync/repository <telegram-bot-url>
apex sync/packages <telegram-bot-url>
```
Формирует единую сводку в&nbsp;Telegram о&nbsp;доступных обновлениях пакетов apt и&nbsp;устаревших образах контейнеров (локальные дайджесты сравниваются с&nbsp;реестром через `skopeo`). Действие ничего не&nbsp;меняет на&nbsp;хосте&nbsp;— это исключительно плановая проверка.
Формирует единую сводку в Telegram о доступных обновлениях пакетов apt и устаревших образах контейнеров (локальные дайджесты сравниваются с реестром через `skopeo`). Действие ничего не меняет на хосте — это исключительно плановая проверка.
## Извлечение сертификатов {#certs}
@@ -71,11 +71,11 @@ apex sync/packages <telegram-bot-url>
apex utils/extract-traefik-certs <acme.json> <dest-dir> [telegram-bot-url]
```
Экспортирует сертификаты из&nbsp;ACME-хранилища граничного прокси в&nbsp;пары `fullchain.pem` и&nbsp;`privkey.pem` для&nbsp;каждого домена (с&nbsp;помощью утилиты в&nbsp;контейнере). Скрипт ограничивает права доступа к&nbsp;ключам до&nbsp;`600` и&nbsp;передаёт владение файлами запустившему пользователю. Это нужно сервисам, которым требуются исходные PEM-файлы вместо готового TLS-соединения от&nbsp;прокси.
Экспортирует сертификаты из ACME-хранилища граничного прокси в пары `fullchain.pem` и `privkey.pem` для каждого домена (с помощью утилиты в контейнере). Скрипт ограничивает права доступа к ключам до `600` и передаёт владение файлами запустившему пользователю. Это нужно сервисам, которым требуются исходные PEM-файлы вместо готового TLS-соединения от прокси.
---
**См.&nbsp;также:**
**См. также:**
- [Getting started with an apex node](/apex/getting-started)
- [Reference: Actions](/apex/reference/actions)
@@ -1,14 +1,14 @@
---
title: Сборка репозитория узла
description: Соберите четыре опоры, зафиксируйте commons и подключите поток capture-up.
description: Соберите четыре опоры, зафиксируйте commons и подключите поток capture-up.
weight: 21
---
**Контекст:** у&nbsp;каждого [узла](/apex/glossary#node)&nbsp;— ровно один репозиторий. Название репозитория может быть любым: [идентичность](/apex/glossary#node-identity) задаётся явно в&nbsp;файле `node.env`, поэтому имя репозитория служит лишь удобным обозначением. В&nbsp;этом руководстве мы пошагово соберём такой репозиторий с&nbsp;нуля.
**Контекст:** у каждого [узла](/apex/glossary#node) — ровно один репозиторий. Название репозитория может быть любым: [идентичность](/apex/glossary#node-identity) задаётся явно в файле `node.env`, поэтому имя репозитория служит лишь удобным обозначением. В этом руководстве мы пошагово соберём такой репозиторий с нуля.
{{% steps %}}
### Инициализируйте репозиторий и&nbsp;зафиксируйте commons
### Инициализируйте репозиторий и зафиксируйте commons
```bash
git init <node-repo> && cd <node-repo>
@@ -18,7 +18,7 @@ git add .gitmodules commons
mkdir -p proprietaries/actions proprietaries/lib configs compositions/apex .github/workflows
```
Ссылка gitlink фиксирует точную версию движка, на&nbsp;которой работает узел. Чтобы обновить движок в&nbsp;будущем, достаточно переключиться на&nbsp;новый тег внутри каталога `commons/` и&nbsp;закоммитить смещение указателя.
Ссылка gitlink фиксирует точную версию движка, на которой работает узел. Чтобы обновить движок в будущем, достаточно переключиться на новый тег внутри каталога `commons/` и закоммитить смещение указателя.
### Объявите идентичность
@@ -30,14 +30,14 @@ APEX_GIT_AUTHOR_NAME=apex [bot]
APEX_GIT_AUTHOR_EMAIL=apex@localhost
```
Файл `node.env` явно задаёт параметры идентификации узла (доменная зона не&nbsp;подразумевается заранее):
Файл `node.env` явно задаёт параметры идентификации узла (доменная зона не подразумевается заранее):
- `APEX_NODE_FQDN`&nbsp;— FQDN хоста для&nbsp;маршрутизации Traefik по&nbsp;правилу `Host()` (если не&nbsp;указан, движок выведет предупреждение и&nbsp;возьмёт имя хоста из&nbsp;ОС).
- `APEX_NODE_HOST`&nbsp;— публичный адрес хоста для&nbsp;параметра `--host` резервного копирования и&nbsp;метки `instance` в&nbsp;системе мониторинга и&nbsp;уведомлениях (по&nbsp;умолчанию совпадает с&nbsp;`APEX_NODE_FQDN`). Первая часть FQDN до&nbsp;точки используется как короткое имя (`name`) узла.
- `APEX_SUBNET`&nbsp;— выделенная подсеть /24 для&nbsp;внутренней сети `direct`. Параметр **обязателен**: без&nbsp;него запуск завершится с&nbsp;кодом&nbsp;66 (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)).
- `APEX_GIT_AUTHOR_NAME` и&nbsp;`APEX_GIT_AUTHOR_EMAIL`&nbsp;— имя и&nbsp;почта сервисного бота capture-up (если не&nbsp;заданы, применяются значения по&nbsp;умолчанию).
- `APEX_NODE_FQDN` — FQDN хоста для маршрутизации Traefik по правилу `Host()` (если не указан, движок выведет предупреждение и возьмёт имя хоста из ОС).
- `APEX_NODE_HOST` — публичный адрес хоста для параметра `--host` резервного копирования и метки `instance` в системе мониторинга и уведомлениях (по умолчанию совпадает с `APEX_NODE_FQDN`). Первая часть FQDN до точки используется как короткое имя (`name`) узла.
- `APEX_SUBNET` — выделенная подсеть /24 для внутренней сети `direct`. Параметр **обязателен**: без него запуск завершится с кодом 66 (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)).
- `APEX_GIT_AUTHOR_NAME` и `APEX_GIT_AUTHOR_EMAIL` — имя и почта сервисного бота capture-up (если не заданы, применяются значения по умолчанию).
### Напишите базовую обёртку и&nbsp;файлы переменных окружения
### Напишите базовую обёртку и файлы переменных окружения
```yaml {filename="compositions/apex/docker-compose.yml"}
name: apex
@@ -60,11 +60,11 @@ APEX_OBS_BASIC_AUTH_USER=<node-user>
# APEX_RESTIC_AWS_ENDPOINT= # напр., URL S3-совместимого эндпоинта
```
Файл `apex.env` хранит только публичные параметры конфигурации, которые безопасно коммитить в&nbsp;репозиторий. Переменная `COMPOSE_PROFILES` определяет набор опциональных базовых сервисов: `adguard`, `xray`, `alloy` (агент сбора метрик и&nbsp;логов) и&nbsp;`manual` (разовые запуски бэкапа); список может быть пустым.
Файл `apex.env` хранит только публичные параметры конфигурации, которые безопасно коммитить в репозиторий. Переменная `COMPOSE_PROFILES` определяет набор опциональных базовых сервисов: `adguard`, `xray`, `alloy` (агент сбора метрик и логов) и `manual` (разовые запуски бэкапа); список может быть пустым.
Все секреты, которые нельзя хранить в&nbsp;Git, задокументируйте в&nbsp;`compositions/apex/SECRETS.md`. Это поможет при&nbsp;развёртывании нового узла заполнить локальный файл `.env`: пароль `APEX_RESTIC_PASSWORD`, ключи доступа к&nbsp;S3, токен `APEX_TRAEFIK_CF_DNS_API_TOKEN`, почту `APEX_TRAEFIK_ACME_EMAIL`, а&nbsp;также `APEX_OBS_BASIC_AUTH_PASS` (если включён мониторинг).
Все секреты, которые нельзя хранить в Git, задокументируйте в `compositions/apex/SECRETS.md`. Это поможет при развёртывании нового узла заполнить локальный файл `.env`: пароль `APEX_RESTIC_PASSWORD`, ключи доступа к S3, токен `APEX_TRAEFIK_CF_DNS_API_TOKEN`, почту `APEX_TRAEFIK_ACME_EMAIL`, а также `APEX_OBS_BASIC_AUTH_PASS` (если включён мониторинг).
### Настройте правила игнорирования для&nbsp;Git
### Настройте правила игнорирования для Git
```gitignore {filename=".gitignore"}
**/@tier1
@@ -78,15 +78,15 @@ __pycache__/
### Добавьте bootstrap-скрипт
Скрипт `init.sh` подготавливает свежий клон репозитория на&nbsp;хосте: выполняет `git submodule update --init --recursive`, настраивает хуки `git config core.hooksPath commons/githooks`, добавляет репозиторий в&nbsp;доверенные (`git config --global --add safe.directory <repo>`) и&nbsp;прописывает `<repo>/commons` в&nbsp;переменную `PATH` командной оболочки, чтобы утилита `apex` была доступна глобально. Не&nbsp;забудьте выставить флаг исполняемого файла: `chmod +x init.sh`.
Скрипт `init.sh` подготавливает свежий клон репозитория на хосте: выполняет `git submodule update --init --recursive`, настраивает хуки `git config core.hooksPath commons/githooks`, добавляет репозиторий в доверенные (`git config --global --add safe.directory <repo>`) и прописывает `<repo>/commons` в переменную `PATH` командной оболочки, чтобы утилита `apex` была доступна глобально. Не забудьте выставить флаг исполняемого файла: `chmod +x init.sh`.
### Заведите реестр адресации
Создайте файл `compositions/ADDRESSING.md` с&nbsp;подсетью узла (`APEX_SUBNET`) и&nbsp;схемой распределения адресов: диапазоны `.10`–`.19` зарезервированы за&nbsp;базовыми сервисами (core), а&nbsp;`.20` и&nbsp;выше выделяются под&nbsp;проекты. Все назначаемые в&nbsp;будущем [якорные IP](/apex/glossary#anchor-ip) фиксируются в&nbsp;этом документе.
Создайте файл `compositions/ADDRESSING.md` с подсетью узла (`APEX_SUBNET`) и схемой распределения адресов: диапазоны `.10`–`.19` зарезервированы за базовыми сервисами (core), а `.20` и выше выделяются под проекты. Все назначаемые в будущем [якорные IP](/apex/glossary#anchor-ip) фиксируются в этом документе.
### Подключите CI capture-up
Настройте периодический CI-workflow, который автоматически вливает ветку `sync/<node>` в&nbsp;`main` (если ветка существует) с&nbsp;авторством бота. Шаг checkout должен выполняться **без** загрузки подмодулей: слияние только сдвигает указатель `commons`, а&nbsp;SSH-ссылка на&nbsp;подмодуль недоступна по&nbsp;стандартному CI-токену. Полный цикл описан в&nbsp;разделе [Concept: Framework architecture](/apex/concepts/architecture) ([capture-up](/apex/glossary#capture-up)).
Настройте периодический CI-workflow, который автоматически вливает ветку `sync/<node>` в `main` (если ветка существует) с авторством бота. Шаг checkout должен выполняться **без** загрузки подмодулей: слияние только сдвигает указатель `commons`, а SSH-ссылка на подмодуль недоступна по стандартному CI-токену. Полный цикл описан в разделе [Concept: Framework architecture](/apex/concepts/architecture) ([capture-up](/apex/glossary#capture-up)).
### Проверьте работу
@@ -94,18 +94,18 @@ __pycache__/
./commons/apex --help
```
В&nbsp;списке должны появиться все базовые действия из&nbsp;commons, а&nbsp;позже&nbsp;— и&nbsp;локальные действия из&nbsp;proprietaries. При&nbsp;запуске вне хоста (до&nbsp;заполнения `node.env`) появится предупреждение о&nbsp;том, что `APEX_NODE_FQDN` не&nbsp;задан: в&nbsp;этом случае движок подставит системное имя хоста. При&nbsp;этом переменная `APEX_SUBNET` обязательно должна быть указана, иначе выполнение завершится с&nbsp;кодом&nbsp;66. Закоммитьте подготовленный репозиторий в&nbsp;ветку `main`.
В списке должны появиться все базовые действия из commons, а позже — и локальные действия из proprietaries. При запуске вне хоста (до заполнения `node.env`) появится предупреждение о том, что `APEX_NODE_FQDN` не задан: в этом случае движок подставит системное имя хоста. При этом переменная `APEX_SUBNET` обязательно должна быть указана, иначе выполнение завершится с кодом 66. Закоммитьте подготовленный репозиторий в ветку `main`.
{{% /steps %}}
## Заметки
- Каталог `proprietaries/` изначально может быть пустым (сохраните структуру с&nbsp;помощью файлов `.gitkeep`): движок корректно работает без&nbsp;пользовательских переопределений.
- Переносите конфигурационные файлы хоста в&nbsp;`configs/` (`ufw/`, `cron/crontab`, `systemd/`, `crowdsec/`) по&nbsp;мере подключения шагов `configure/*`. Действия считывают шаблоны из&nbsp;`configs/` напрямую.
- Каталог `proprietaries/` изначально может быть пустым (сохраните структуру с помощью файлов `.gitkeep`): движок корректно работает без пользовательских переопределений.
- Переносите конфигурационные файлы хоста в `configs/` (`ufw/`, `cron/crontab`, `systemd/`, `crowdsec/`) по мере подключения шагов `configure/*`. Действия считывают шаблоны из `configs/` напрямую.
---
**См.&nbsp;также:**
**См. также:**
- [Getting started with an apex node](/apex/getting-started)
- [Concept: Framework architecture](/apex/concepts/architecture)
@@ -1,16 +1,16 @@
---
title: Добавление композиции сервисов
description: "Подключите новый проект к узлу: якорный IP, сети, уровни и линтинг."
description: "Подключите новый проект к узлу: якорный IP, сети, уровни и линтинг."
weight: 23
---
**Контекст:** проект подключается к&nbsp;[узлу](/apex/glossary#node) в&nbsp;виде отдельного каталога в&nbsp;`compositions/` со&nbsp;своим compose-файлом. Требования фреймворка к&nbsp;этому файлу минимальны: статический [якорный IP](/apex/glossary#anchor-ip) в&nbsp;сети `direct`, подключение к&nbsp;внешним сетям по&nbsp;имени и&nbsp;пути к&nbsp;данным относительно уровней хранения.
**Контекст:** проект подключается к [узлу](/apex/glossary#node) в виде отдельного каталога в `compositions/` со своим compose-файлом. Требования фреймворка к этому файлу минимальны: статический [якорный IP](/apex/glossary#anchor-ip) в сети `direct`, подключение к внешним сетям по имени и пути к данным относительно уровней хранения.
{{% steps %}}
### Выделите якорный адрес
Выберите следующий свободный адрес `.20` или выше в&nbsp;подсети узла и&nbsp;зафиксируйте его в&nbsp;`compositions/ADDRESSING.md`. Диапазон `.10`–`.19` зарезервирован для&nbsp;[базовой композиции](/apex/glossary#core-composition) (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)).
Выберите следующий свободный адрес `.20` или выше в подсети узла и зафиксируйте его в `compositions/ADDRESSING.md`. Диапазон `.10`–`.19` зарезервирован для [базовой композиции](/apex/glossary#core-composition) (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)).
### Напишите файл compose
@@ -52,14 +52,14 @@ networks:
Применяемые соглашения:
- **Один якорь на&nbsp;проект**: контейнер-якорь получает постоянный `ipv4_address` в&nbsp;сети `direct`, а&nbsp;вспомогательные sidecar-контейнеры используют его сетевой стек через `network_mode: service:<anchor>`.
- **Внешние сети подключаются по&nbsp;имени**: для&nbsp;сетей `direct`, `enclave` и&nbsp;`socket` указывается `external: true`, поскольку они объявляются в&nbsp;базовой композиции.
- **Публикация через `enclave` и&nbsp;метки Traefik**: подключение к&nbsp;сети `enclave` вместе с&nbsp;метками маршрутизации открывает сервис через граничный прокси. Прокси связывается с&nbsp;ним через socket-прокси, без&nbsp;проброса портов наружу.
- **Пути к&nbsp;данным привязаны к&nbsp;уровням**: пути вида `./@tierN/...` разрешаются через символические ссылки, которые создаёт действие `tiers/link`.
- **Резервное копирование настраивается метками `restic.*`**: действие `apex backup/run` запускает утилиту [resticontainer](https://github.com/deytenit/resticontainer). Сервис помечается меткой `restic.enable=true`, а&nbsp;пути монтирования для&nbsp;бэкапа перечисляются в&nbsp;`restic.backup.paths=<container-path>` (resticontainer сопоставляет их с&nbsp;путями на&nbsp;хосте). Сервисы без&nbsp;этих меток игнорируются. Сохраняйте только восстановимые данные и&nbsp;конфигурацию.
- **Идентификаторы UID и&nbsp;GID**: переменные `${APEX_UID...}` берутся из&nbsp;файла `.env` проекта, который формирует действие `tiers/useradd`.
- **Один якорь на проект**: контейнер-якорь получает постоянный `ipv4_address` в сети `direct`, а вспомогательные sidecar-контейнеры используют его сетевой стек через `network_mode: service:<anchor>`.
- **Внешние сети подключаются по имени**: для сетей `direct`, `enclave` и `socket` указывается `external: true`, поскольку они объявляются в базовой композиции.
- **Публикация через `enclave` и метки Traefik**: подключение к сети `enclave` вместе с метками маршрутизации открывает сервис через граничный прокси. Прокси связывается с ним через socket-прокси, без проброса портов наружу.
- **Пути к данным привязаны к уровням**: пути вида `./@tierN/...` разрешаются через символические ссылки, которые создаёт действие `tiers/link`.
- **Резервное копирование настраивается метками `restic.*`**: действие `apex backup/run` запускает утилиту [resticontainer](https://github.com/deytenit/resticontainer). Сервис помечается меткой `restic.enable=true`, а пути монтирования для бэкапа перечисляются в `restic.backup.paths=<container-path>` (resticontainer сопоставляет их с путями на хосте). Сервисы без этих меток игнорируются. Сохраняйте только восстановимые данные и конфигурацию.
- **Идентификаторы UID и GID**: переменные `${APEX_UID...}` берутся из файла `.env` проекта, который формирует действие `tiers/useradd`.
### Настройте хранилище и&nbsp;права доступа
### Настройте хранилище и права доступа
```bash
apex tiers/link <tier1> <tier2> <tier3>
@@ -67,7 +67,7 @@ apex tiers/useradd
apex tiers/chown
```
Повторный запуск этих команд идемпотентен: движок находит новый проект, создаёт для&nbsp;него каталоги хранения и&nbsp;файл `.env`, заводит системного пользователя `noroot-<project>` и&nbsp;выставляет права доступа.
Повторный запуск этих команд идемпотентен: движок находит новый проект, создаёт для него каталоги хранения и файл `.env`, заводит системного пользователя `noroot-<project>` и выставляет права доступа.
### Проверьте конфигурацию
@@ -75,7 +75,7 @@ apex tiers/chown
apex utils/lint-docker-compose
```
Действие проверяет корректность, уникальность и&nbsp;принадлежность подсети для&nbsp;каждого якорного IP, после чего запускает проверку compose-файлов по&nbsp;всему репозиторию. Тот&nbsp;же линтинг автоматически выполняется хуком pre-commit для&nbsp;всех подготовленных к&nbsp;коммиту файлов.
Действие проверяет корректность, уникальность и принадлежность подсети для каждого якорного IP, после чего запускает проверку compose-файлов по всему репозиторию. Тот же линтинг автоматически выполняется хуком pre-commit для всех подготовленных к коммиту файлов.
### Разверните сервисы
@@ -83,18 +83,18 @@ apex utils/lint-docker-compose
apex compose up
```
Базовая композиция уже работает, поэтому новый проект запустится в&nbsp;порядке своей очереди по&nbsp;алфавиту. Проверьте статус контейнеров через `docker ps`, а&nbsp;доступность маршрута&nbsp;— через граничный прокси.
Базовая композиция уже работает, поэтому новый проект запустится в порядке своей очереди по алфавиту. Проверьте статус контейнеров через `docker ps`, а доступность маршрута — через граничный прокси.
{{% /steps %}}
## Заметки
- Секретные данные проекта (например, пароль к&nbsp;базе данных) хранятся в&nbsp;собственном `.env` на&nbsp;уровне 1&nbsp;(@tier1), рядом с&nbsp;автоматически сгенерированным блоком UID.
- Не&nbsp;объявляйте сети `direct`, `enclave` и&nbsp;`socket` заново внутри файла проекта: дублирование определений приведёт к&nbsp;конфликту с&nbsp;базовой композицией за&nbsp;управление сетью.
- Секретные данные проекта (например, пароль к базе данных) хранятся в собственном `.env` на уровне 1 (@tier1), рядом с автоматически сгенерированным блоком UID.
- Не объявляйте сети `direct`, `enclave` и `socket` заново внутри файла проекта: дублирование определений приведёт к конфликту с базовой композицией за управление сетью.
---
**См.&nbsp;также:**
**См. также:**
- [Concept: Compositions and the shared core](/apex/concepts/compositions)
- [Concept: Tiered storage and privilege isolation](/apex/concepts/tiered-storage)