fix, feat, doc: massive build system refactorion, bug fixes and apex documentation (#19)
This commit is contained in:
1 parent
e8b42c60c2
commit
91f5245823
165 files changed
+8598
-1205
No files matched your search
@@ -7,6 +7,7 @@ title: 'Документация ermnvldmr.com'
|
||||
Добро пожаловать в единую документацию для моих собственных проектов
|
||||
|
||||
{{< cards >}}
|
||||
{{< card link="root" title="ermnvldmr.com-root-commons" icon="server" subtitle="Документация общих модулей корневой инфраструктуры ermnvldmr.com" >}}
|
||||
{{< card link="/en/apex" title="apex.ermnvldmr.com" icon="server" subtitle="Fleet operations framework (English only)" >}}
|
||||
{{< card link="quetza" title="Quetza Bot" icon="terminal" subtitle="Документация модульного фреймворка Discord-бота" >}}
|
||||
{{< card link="root" title="ermnvldmr.com-root-commons (deprecated)" icon="archive" subtitle="Устарело — заменено фреймворком apex" >}}
|
||||
{{< /cards >}}
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Документация фреймворка операций флота apex
|
||||
description: "Документация фреймворка операций флота apex: движок на stdlib-python и общее ядро compose для self-hosted узлов."
|
||||
weight: 1
|
||||
type: docs
|
||||
cascade:
|
||||
type: docs
|
||||
---
|
||||
|
||||
`apex` — это [commons](/apex/glossary#commons) (общий репозиторий) флота self-hosted [узлов](/apex/glossary#node): [движок](/apex/glossary#engine) на python3-stdlib, который запускает сгруппированные [действия](/apex/glossary#action) как `apex <group>/<action>`, в паре с общим, параметризуемым через `${env}` ядром docker-compose. Каждый репозиторий узла подключает фреймворк как закреплённый git-субмодуль `commons/`. apex приходит на смену bash-фреймворку commons под названием `root`.
|
||||
|
||||
Стек намеренно минимален: `git` плюс стандартная библиотека Python 3. Здесь нет ни `pip`, ни Node, ни Go, ни CUE, а compose-файлы написаны вручную — без кодогенерации.
|
||||
|
||||
## Основные возможности
|
||||
|
||||
- **Единая точка входа в локальном чекауте** — `apex` всегда действует на том чекауте, из которого он запущен; аргумент `<node>` передавать не нужно.
|
||||
- **Явная идентичность из `node.env`** — [идентичность узла](/apex/glossary#node-identity) объявляется для каждого узла в `node.env` (`APEX_NODE_FQDN`, `APEX_NODE_HOST`, `APEX_SUBNET`); фреймворк не предполагает какого-либо конкретного домена, а короткое `name` узла — это просто первая метка хоста.
|
||||
- **Репозитории узлов из четырёх опор** — каждый репозиторий узла имеет одинаковую структуру: закреплённый субмодуль `commons/`, локальные [проприетарные модули узла](/apex/glossary#proprietaries), его [композиции](/apex/glossary#compositions) и `node.env`.
|
||||
- **Проприетарный оверлей** — локальные проприетарные модули узла [накладываются](/apex/glossary#overlay) поверх общих действий; commons содержит только то, что разделяют все узлы.
|
||||
- **Многоуровневое хранилище с изоляцией привилегий** — стандартизированные [уровни](/apex/glossary#tier) хранилища с изоляцией привилегий на уровне проекта.
|
||||
- **Общая базовая композиция** — [базовая композиция](/apex/glossary#core-composition) (`compositions/apex/docker-compose.yml`) параметризуется через `${env}` и включается в композиции каждого узла.
|
||||
|
||||
## Разделы
|
||||
|
||||
{{% cards %}}
|
||||
{{< card link="getting-started" title="Начало работы" icon="map" subtitle="Предварительные требования, начальная настройка и первые действия на новом узле." >}}
|
||||
{{< card link="concepts" title="Концепции" icon="book-open" subtitle="Архитектура, идентичность, оверлей и модель хранилища." >}}
|
||||
{{< card link="guides" title="Руководства" icon="map" subtitle="Практические руководства по эксплуатации и расширению узла." >}}
|
||||
{{< card link="reference" title="Справочник" icon="code" subtitle="Действия, вспомогательная библиотека движка и коды выхода." >}}
|
||||
{{< card link="troubleshooting" title="Устранение неполадок" icon="shield-check" subtitle="Симптомы, диагностика и исправление типовых сбоев." >}}
|
||||
{{% /cards %}}
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Концепции
|
||||
description: "Архитектура, идентичность, действия, контекст выполнения, композиции, хранилище, адресация и безопасность."
|
||||
weight: 10
|
||||
---
|
||||
|
||||
Теоретические основы фреймворка операций флота apex: как [узел](/apex/glossary#node) объявляет свою [идентичность](/apex/concepts/node-identity), как выполняются [действия](/apex/glossary#action) и как узел организует свои сервисы, хранилище и адресацию.
|
||||
|
||||
{{% cards %}}
|
||||
{{< card link="architecture" title="Архитектура" icon="share" subtitle="Слои движка, commons и configs и как они сочетаются друг с другом." >}}
|
||||
{{< card link="node-identity" title="Идентичность узла" icon="finger-print" subtitle="Как узел объявляет, кто он и где ему место." >}}
|
||||
{{< card link="actions-and-overlay" title="Действия и оверлей" icon="lightning-bolt" subtitle="Общие действия и локальные переопределения узла." >}}
|
||||
{{< card link="execution-context" title="Контекст выполнения" icon="terminal" subtitle="Окружение, которое каждое действие получает во время выполнения." >}}
|
||||
{{< card link="compositions" title="Композиции" icon="cube" subtitle="Как стеки сервисов объявляются и разворачиваются на каждом узле." >}}
|
||||
{{< card link="tiered-storage" title="Многоуровневое хранилище" icon="database" subtitle="Стандартизированная классификация хранилища для данных сервисов." >}}
|
||||
{{< card link="addressing" title="Адресация" icon="globe-alt" subtitle="Подсеть узла, якорные IP и соглашение об анклаве." >}}
|
||||
{{< card link="security-model" title="Модель безопасности" icon="shield-check" subtitle="Изоляция привилегий и усиление защиты сети." >}}
|
||||
{{% /cards %}}
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Руководства: Эксплуатация флота apex](/apex/guides)
|
||||
- [Справочник: Команды и интерфейсы](/apex/reference)
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: "Действия и оверлей"
|
||||
description: "Контракт действия и как узло-локальные проприетарии затеняют действия commons."
|
||||
weight: 13
|
||||
---
|
||||
|
||||
Каждая единица автоматизации в apex — это [действие](/apex/glossary#action): один Python-модуль, который [движок](/apex/glossary#engine) диспетчеризует по имени. [Оверлей](/apex/glossary#overlay) позволяет [проприетариям](/apex/glossary#proprietaries) узла добавлять, переопределять или отключать действия [«commons»](/apex/glossary#commons), не форкая их. Эта страница определяет контракт действия, декларативные дескрипторы аргументов и правила разрешения.
|
||||
|
||||
## Контракт действия {#contract}
|
||||
|
||||
Действие — это один файл `.py` в дереве `actions/`. Путь к файлу **является** именем действия: `actions/<group>/<action>.py` вызывается как `apex <group>/<action> [args...]`. Нет реестра, который нужно обновлять — размещение файла в дереве публикует действие.
|
||||
|
||||
Модуль должен предоставлять две вещи:
|
||||
|
||||
- **`METADATA = Meta(...)`** → краткое описание и дескрипторы аргументов, которые управляют как текстом справки, так и парсингом. Система дескрипторов пришла на смену heredoc-у `action_metadata()` bash-фреймворка «root» commons, предшественника apex.
|
||||
- **`run(ctx, args)`** → точка входа. `ctx` — это построенный движком контекст выполнения; `args` — распарсенное пространство имён `argparse`.
|
||||
|
||||
```python {filename="actions/<group>/<action>.py"}
|
||||
from engine.descriptor import Meta, Arg, Flag
|
||||
|
||||
METADATA = Meta(
|
||||
summary="Однострочное описание, показываемое в перечислении действий",
|
||||
args=[
|
||||
Arg("target", help="Обязательный позиционный аргумент"),
|
||||
Flag("--dry-run", help="Булев флаг"),
|
||||
],
|
||||
)
|
||||
|
||||
def run(ctx, args):
|
||||
...
|
||||
return 0
|
||||
```
|
||||
|
||||
Возвращаемое значение `run` становится кодом завершения процесса. Нецелочисленный возврат (включая `None`) трактуется как успех (`0`). Необработанное исключение логируется с префиксом контекста и завершается с кодом `1`.
|
||||
|
||||
> [!NOTE]
|
||||
> `METADATA` технически необязательна — модуль без неё диспетчеризуется с пустым `Meta` и не принимает аргументов — но каждое настоящее действие должно её объявлять, поскольку вывод в перечислении и `--help` генерируется из неё.
|
||||
|
||||
## Декларативные аргументы {#arguments}
|
||||
|
||||
`Meta.args` — это список дескрипторов. Каждый отображается ровно на одну конструкцию `argparse`:
|
||||
|
||||
| Дескриптор | Отображается на | Поведение |
|
||||
| --- | --- | --- |
|
||||
| `Arg("name")` | позиционный аргумент | Обязателен по умолчанию; `required=False` делает его опциональным позиционным (`nargs="?"`, по умолчанию `None`). |
|
||||
| `Opt("--name", default=...)` | опция со значением | Опциональный флаг, принимающий значение, с объявленным значением по умолчанию. |
|
||||
| `Flag("--name")` | булев флаг | `store_true`; наличие означает `True`. |
|
||||
| `Rest("name")` | остаток | Жадная сквозная передача всех оставшихся argv (`argparse.REMAINDER`). |
|
||||
|
||||
Движок строит парсер как `apex <group>/<action>` и парсит оставшиеся argv перед вызовом `run`. `--help` обрабатывается на этапе парсинга. Ошибка использования — неизвестный флаг, отсутствующий позиционный аргумент — печатает usage и завершается с кодом `64` (`EX_USAGE`) вместо стандартного для argparse `2`.
|
||||
|
||||
## Перечисление без импорта {#listing}
|
||||
|
||||
`apex` без аргументов (или `--help`) перечисляет каждое доступное действие. Перечисление никогда не импортирует модули действий: движок статически сканирует каждый файл модулем `ast` и извлекает только краткое описание `METADATA` и любую константу `DISABLED`. Поэтому код действия не может выполниться — или упасть — во время перечисления; файл, который не удаётся распарсить, перечисляется как `(unparseable)`, а не ломает перечисление.
|
||||
|
||||
Каждая запись помечается своим источником оверлея:
|
||||
|
||||
- нет метки → действие commons,
|
||||
- `(local)` → существует только в проприетариях,
|
||||
- `(local override)` → действие проприетариев, затеняющее действие commons,
|
||||
- `[DISABLED: reason]` → действие объявляет `DISABLED`.
|
||||
|
||||
## Оверлей {#overlay}
|
||||
|
||||
Имена действий разрешаются по двум деревьям, сначала проприетарии:
|
||||
|
||||
{{< filetree/container >}}
|
||||
{{< filetree/folder name="node repository" state="open" >}}
|
||||
{{< filetree/file name="node.env" >}}
|
||||
{{< filetree/folder name="commons" state="open" >}}
|
||||
{{< filetree/folder name="engine" state="closed" >}}{{< /filetree/folder >}}
|
||||
{{< filetree/folder name="actions" state="open" >}}
|
||||
{{< filetree/file name="<group>/<action>.py" >}}
|
||||
{{< /filetree/folder >}}
|
||||
{{< /filetree/folder >}}
|
||||
{{< filetree/folder name="proprietaries" state="open" >}}
|
||||
{{< filetree/folder name="actions" state="open" >}}
|
||||
{{< filetree/file name="<group>/<action>.py" >}}
|
||||
{{< /filetree/folder >}}
|
||||
{{< /filetree/folder >}}
|
||||
{{< /filetree/folder >}}
|
||||
{{< /filetree/container >}}
|
||||
|
||||
Движок определяет эту схему по своему собственному расположению: когда он запускается из каталога `commons/`, родитель которого содержит `node.env`, родитель является корнем репозитория [узла](/apex/glossary#node), а `proprietaries/actions/` — деревом оверлея. Автономный checkout commons является собственным корнем и не имеет слоя оверлея. Внутри репозитория узла [идентичность узла](/apex/glossary#node-identity) разрешается из FQDN хоста перед диспетчеризацией.
|
||||
|
||||
Разрешение для заданного имени проверяет `proprietaries/actions/<name>.py` первым и `commons/actions/<name>.py` вторым; ровно один модуль загружается по пути. Неизвестное имя — это ошибка (код завершения `1`). Оверлей поддерживает четыре приёма:
|
||||
|
||||
- **Добавление (Add)** → файл, существующий только в проприетариях, становится узло-локальным действием, перечисляется как `(local)`.
|
||||
- **Переопределение (Override)** → файл проприетариев по тому же относительному пути полностью затеняет реализацию commons; путь commons записывается в разрешении как `shadowed`.
|
||||
- **Обёртывание (Wrap)** → переопределяющий модуль может делегировать реализации, которую он затеняет, через `ctx.commons.run(args)`, добавляя узло-локальное поведение до или после логики commons вместо её замены.
|
||||
- **Отключение (Disable)** → модуль, присваивающий `DISABLED = "reason"`, отклоняется при диспетчеризации: движок логирует причину и завершается с кодом `1`. Перечисление показывает `[DISABLED: reason]`. Голый `DISABLED = True` также отключает, с обобщённой причиной `disabled`; пустая строка не отключает.
|
||||
|
||||
## Коды завершения {#exit-codes}
|
||||
|
||||
Действия и движок следуют фиксированному соглашению о кодах завершения:
|
||||
|
||||
| Код | Значение | Типичная причина |
|
||||
| --- | --- | --- |
|
||||
| `0` | успех | `run` вернул `0` или нецелочисленное значение. |
|
||||
| `1` | общая ошибка | Неизвестное действие, отключённое действие, необработанное исключение, нецелочисленный `SystemExit`. |
|
||||
| `64` | ошибка использования | Аргументы отклонены парсером (`EX_USAGE`). |
|
||||
| `65` | ошибка данных | Некорректные входные данные (`EX_DATAERR`), поднимается библиотеками движка. |
|
||||
| `66` | отсутствующий ввод | Требуемый ввод отсутствует (`EX_NOINPUT`), поднимается библиотеками движка. |
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Архитектура фреймворка](/apex/concepts/architecture)
|
||||
- [Концепция: Контекст выполнения](/apex/concepts/execution-context)
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: "Схема адресации флота"
|
||||
description: "Подсети /24 для каждого узла и статические якорные IP внутри пространства анклава (по умолчанию 198.18.0.0/16)."
|
||||
weight: 17
|
||||
---
|
||||
|
||||
Apex назначает каждому сервису фиксированный, предсказуемый IPv4-адрес внутри подсети узла. Каждый узел объявляет собственный `/24`, якоря сервисов — это статические адреса внутри него, а вся схема механически принудительно проверяется линтером якорных IP.
|
||||
|
||||
## Адресное пространство {#address-space}
|
||||
|
||||
Пространство [анклава](/apex/glossary#enclave) по умолчанию равно `198.18.0.0/16` — диапазону для бенчмаркинга, выделенному [RFC 2544: Benchmarking Methodology for Network Interconnect Devices](https://datatracker.ietf.org/doc/html/rfc2544). Этот диапазон никогда не маршрутизируется в публичном Интернете и не конфликтует с распространёнными частными диапазонами (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), которые уже используют облачные провайдеры и домашние сети. Оператор может переопределить его через `APEX_ENCLAVE_SUBNET`.
|
||||
|
||||
## Подсети узлов {#node-subnets}
|
||||
|
||||
Каждый [узел](/apex/glossary#node) объявляет ровно один `/24` как `APEX_SUBNET` в своём `node.env`, и эта подсеть является диапазоном IPAM сети `direct`. Оператор назначает её; по соглашению она располагается внутри пространства анклава (`198.18.0.0/16` по умолчанию или того, что задано в `APEX_ENCLAVE_SUBNET`), но фреймворк не накладывает на этот выбор никакой арифметики.
|
||||
|
||||
> [!NOTE]
|
||||
> В качестве опционального соглашения оператора вы можете организовать `/24` узлов в непрерывные блоки (например, группируя связанные узлы), чтобы сохранять читаемость распределений. Это вопрос учёта, а не правило фреймворка.
|
||||
|
||||
## Распределение внутри `/24` узла {#node-allocation}
|
||||
|
||||
Внутри `/24` узла адреса хостов разделяются по ролям:
|
||||
|
||||
| Диапазон | Назначение |
|
||||
|---|---|
|
||||
| `.10`–`.19` | Зарезервировано для якорей [базовой композиции](/apex/glossary#core-composition); `traefik` всегда `.10` (через `APEX_TRAEFIK_DIRECT_IP`) |
|
||||
| `.20` и выше | Якоря сервисов, распределяемые по `<project>` |
|
||||
|
||||
Каждый якорь сервиса — это статический `ipv4_address` в сети direct — фиксированный [якорный IP](/apex/glossary#anchor-ip), а не динамически назначаемая аренда. Распределения записываются в `compositions/ADDRESSING.md`, который служит человекочитаемым реестром подсети узла.
|
||||
|
||||
## Принудительная проверка {#enforcement}
|
||||
|
||||
Линтер compose проверяет якорные IP по всем [композициям](/apex/glossary#compositions) на узле. Для каждого `ipv4_address`, найденного в `compositions/*/docker-compose.yml`, он проверяет, что адрес:
|
||||
|
||||
- **Корректно сформирован** — некорректный литерал сообщается как ошибка линтера, а не крах.
|
||||
- **Уникален** — один и тот же якорный IP не может встречаться в двух проектах; дубликаты сообщаются с указанием обоих конфликтующих проектов.
|
||||
- **Внутри подсети узла** — каждый якорь должен попадать в объявленный `/24` узла.
|
||||
|
||||
Любое нарушение приводит к провалу линтера с кодом выхода `1` (общая ошибка). Успешное прохождение проверки логирует `Anchor IP validation passed.` Реализация находится в [действии](/apex/glossary#action) `utils/lint-docker-compose`; см. [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com).
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
- [RFC 2544: Benchmarking Methodology for Network Interconnect Devices](https://datatracker.ietf.org/doc/html/rfc2544)
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Архитектура фреймворка
|
||||
description: Четыре опоры репозиториев узла, субмодуль «commons» и поток диспетчеризации.
|
||||
weight: 11
|
||||
---
|
||||
|
||||
apex разделяет автоматизацию флота на один общий репозиторий — [«commons» (общий репозиторий)](/apex/glossary#commons) — и по одному репозиторию на каждый [узел](/apex/glossary#node). «commons» несёт всё то, что общее для каждого узла: [движок](/apex/glossary#engine), общие [действия](/apex/glossary#action) и [ядровую композицию](/apex/glossary#core-composition). Репозиторий узла несёт всё то, что делает этот узел самим собой: его идентичность, хостовые [конфигурации](/apex/glossary#configs), сервисные [композиции](/apex/glossary#compositions) и узло-локальные [проприетарии](/apex/glossary#proprietaries).
|
||||
|
||||
## Четыре опоры {#pillars}
|
||||
|
||||
Каждый репозиторий узла имеет одинаковую структуру:
|
||||
|
||||
{{< filetree/container >}}
|
||||
{{< filetree/folder name="<node>.<your-domain>" state="open" >}}
|
||||
{{< filetree/folder name="commons" state="closed" >}}{{< /filetree/folder >}}
|
||||
{{< filetree/folder name="proprietaries" state="closed" >}}{{< /filetree/folder >}}
|
||||
{{< filetree/folder name="configs" state="closed" >}}{{< /filetree/folder >}}
|
||||
{{< filetree/folder name="compositions" state="closed" >}}{{< /filetree/folder >}}
|
||||
{{< filetree/file name="node.env" >}}
|
||||
{{< filetree/file name="init.sh" >}}
|
||||
{{< /filetree/folder >}}
|
||||
{{< /filetree/container >}}
|
||||
|
||||
| Опора | Содержимое | Владение |
|
||||
|---|---|---|
|
||||
| `commons/` | Git-субмодуль → `apex.ermnvldmr.com`, закреплённый на релизном теге | Общее, версионируемое |
|
||||
| `proprietaries/` | Узло-локальные действия, библиотеки и исходники инструментов ([накладываются оверлеем](/apex/glossary#overlay) поверх commons) | Узел |
|
||||
| `configs/` | Исходники конфигурации хоста: `ufw/`, `cron/crontab`, `systemd/`, `crowdsec/` | Узел |
|
||||
| `compositions/` | По одному каталогу на проект плюс ядровая обёртка `apex/` и реестр адресации | Узел |
|
||||
|
||||
Закрепление субмодуля — это версионный контракт фреймворка: узел выполняет ровно тот движок и ту ядровую композицию, которые записаны в его gitlink. Обновление узла — это осознанное действие: переключитесь на более новый тег внутри `commons/` и зафиксируйте перемещённый указатель.
|
||||
|
||||
## Поток диспетчеризации {#dispatch}
|
||||
|
||||
Лаунчер `commons/apex` действует на локальный checkout; аргумента узла нет нигде.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["apex <group>/<action> [args]"] --> B{Схема размещения}
|
||||
B -- "каталог commons/ с node.env рядом" --> C[Checkout узла:<br/>оверлей = proprietaries/actions]
|
||||
B -- иначе --> D[Автономный commons:<br/>без оверлея]
|
||||
C --> E["Разрешение оверлея<br/>(сначала proprietaries)"]
|
||||
D --> E
|
||||
E --> F["Разрешение идентичности<br/>(FQDN + node.env)"]
|
||||
F --> G["Построение ctx<br/>(log, sys, tpl, host, ufw,<br/>crowdsec, systemd, notify)"]
|
||||
G --> H["разбор аргументов (Meta)"]
|
||||
H --> I["run(ctx, args)"]
|
||||
```
|
||||
|
||||
Движок определяет схему размещения по своему собственному расположению: запуск из каталога с именем `commons/`, родитель которого содержит `node.env`, означает «checkout узла» — родитель становится корнем репозитория, а `proprietaries/actions/` становится деревом оверлея. Любое другое расположение — это автономный checkout commons, выступающий в роли собственного корня.
|
||||
|
||||
Перечисление (`apex --help`) никогда не импортирует модули действий; краткие описания и маркеры отключённых действий извлекаются статическим сканированием через `ast`. Диспетчеризация загружает ровно один модуль по пути. Подробности в [Концепция: Действия и оверлей](/apex/concepts/actions-and-overlay).
|
||||
|
||||
## Модель «capture-up» {#capture-up}
|
||||
|
||||
Репозитории узлов — это реестры, а не просто источники: хост сам записывает то, что он выполняет.
|
||||
|
||||
1. Запланированный запуск `sync/repository` на хосте фиксирует рабочее дерево в ветку `sync/<node>` (включая указатель субмодуля commons), выполняет rebase на `origin/main` и отправляет с `--force-with-lease`.
|
||||
2. Запланированное CI-задание в репозитории узла вливает `sync/<node>` обратно в `main` под идентичностью бота.
|
||||
|
||||
Этот цикл «capture-up» ([capture-up](/apex/glossary#capture-up)) означает, что дрейф на хосте превращается в коммит — видимый, поддающийся сравнению через diff и откату — вместо тихого расхождения. Синхронизация прерывается вместо фиксации, если субмодуль commons «грязный» или дерево `compositions/` отсутствует, поэтому сломанный checkout никогда не становится записанным состоянием.
|
||||
|
||||
> [!NOTE]
|
||||
> CI-задание слияния выполняет checkout репозитория *без* субмодулей: субмодуль commons использует SSH-URL, который стандартный CI-токен не может получить, и слияние в любом случае лишь перемещает указатель gitlink.
|
||||
|
||||
## Проектные ограничения {#constraints}
|
||||
|
||||
- **Только stdlib.** Движок работает на штатном Python 3 из Debian — без `pip`, без сторонних импортов. Опциональный каталог `vendor/` существует для закреплённого чистого Python-кода, но поставляется пустым.
|
||||
- **Composes, написанные вручную.** Нет слоя манифестов или генерации кода; compose-файлы являются источником истины.
|
||||
- **Свидетельство важнее соглашения.** Всё, что может быть обеспечено принудительно, обеспечивается действием (линт якорного IP, откат crontab, самовосстановление блоков-маркеров), а не документацией.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Действия и оверлей](/apex/concepts/actions-and-overlay)
|
||||
- [Концепция: Разрешение идентичности узла](/apex/concepts/node-identity)
|
||||
- [Руководство: Сборка репозитория узла](/apex/guides/node-repository)
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: Композиции и общее ядро
|
||||
description: Ядровая композиция на основе include, профили, сети и наслоение окружения.
|
||||
weight: 15
|
||||
---
|
||||
|
||||
Среда выполнения узла объявляется как [композиции](/apex/glossary#compositions): по одному docker-compose-проекту на сервис в каталоге `compositions/<project>/`, плюс [ядровая композиция](/apex/glossary#core-composition) — граничные и инфраструктурные сервисы, необходимые каждому узлу. Ядро располагается в [«commons»](/apex/glossary#commons) и потребляется каждым узлом через тонкую обёртку, поэтому его определение написано один раз и версионируется вместе с фреймворком.
|
||||
|
||||
## Обёртка include {#include}
|
||||
|
||||
Файл `compositions/apex/docker-compose.yml` каждого узла состоит из трёх строк:
|
||||
|
||||
```yaml {filename="compositions/apex/docker-compose.yml"}
|
||||
name: apex
|
||||
include:
|
||||
- ../../commons/compositions/apex/docker-compose.yml
|
||||
```
|
||||
|
||||
Общий файл полностью параметризован через `${APEX_*}` и не содержит относительных хостовых путей — каждое монтирование разрешается через переменные, которые [движок](/apex/glossary#engine) экспортирует при вызове, поэтому один и тот же файл корректен на каждом узле.
|
||||
|
||||
Структурное исключение — узел, которому действительно нужно изменить ядровой сервис, например опубликовать дополнительный хостовый порт, — наслаивает переопределение через форму include со списком путей вместо редактирования общего файла:
|
||||
|
||||
```yaml {filename="compositions/apex/docker-compose.yml"}
|
||||
name: apex
|
||||
include:
|
||||
- path:
|
||||
- ../../commons/compositions/apex/docker-compose.yml
|
||||
- ./traefik.override.yml
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Директива `include:` требует Docker Compose v2.20 или новее.
|
||||
|
||||
## Ядровые сервисы и профили {#core-services}
|
||||
|
||||
Ядровая композиция определяет общий набор сервисов флота. Всегда включённые сервисы:
|
||||
|
||||
- **traefik** — граничный обратный прокси и якорь сети `direct` (всегда `.10` в подсети узла). Он завершает TLS через DNS-челлендж, обнаруживает бэкенды через docker-метки и выставляет свою панель управления на FQDN узла за списком разрешённых IP [анклава](/apex/glossary#enclave).
|
||||
- **socket-proxy** — отфильтрованный прокси для docker-сокета; traefik читает состояние контейнеров через него и никогда не обращается к сокету напрямую.
|
||||
|
||||
Опциональные сервисы, управляемые через compose-профили, которые узел включает в `COMPOSE_PROFILES` (задаётся в `apex.env`):
|
||||
|
||||
| Профиль | Сервис | Роль |
|
||||
|---|---|---|
|
||||
| `xray` | xray | Прокси-край с host-network для анклава. |
|
||||
| `adguard` | adguard | DNS-фильтрация с использованием сетевого пространства имён traefik; маршрутизируется по `/adguard` на FQDN узла. |
|
||||
| `alloy` | alloy | Понодовый агент наблюдаемости (Grafana Alloy). Собирает метрики хоста и контейнеров, а также логи journal и контейнеров и **проталкивает** их исходящим потоком в центральные Prometheus и Loki (см. [Концепция: Модель безопасности](/apex/concepts/security-model#observability)). Его `hostname` контейнера установлен в `APEX_NODE_HOST`, и это значение проставляет метки `node` и `instance` на каждой метрике и потоке логов. |
|
||||
| `manual` | resticontainer | Одноразовый исполнитель резервного копирования, управляемый метками (`ghcr.io/deytenit/resticontainer`) — никогда не запускается через `up`; вызывается действием `backup/run`, которое обнаруживает намерение резервного копирования для каждого сервиса из compose-меток `restic.*`. |
|
||||
|
||||
## Сети {#networks}
|
||||
|
||||
Ядровая композиция владеет тремя сетями узла; сервисные композиции ссылаются на них по ключу с `external: true`.
|
||||
|
||||
| Сеть | Тип | Назначение |
|
||||
|---|---|---|
|
||||
| `direct` | bridge, подсеть = `APEX_SUBNET` | Сеть со статическими [якорными IP](/apex/glossary#anchor-ip); якорь каждого проекта удерживает здесь фиксированный адрес (см. [Концепция: Схема адресации флота](/apex/concepts/addressing)). |
|
||||
| `enclave` | bridge, `internal: true` | Бэкенд-сеть обратного прокси; traefik достигает помеченных сервисов здесь. |
|
||||
| `socket` | bridge, `internal: true` | Несёт только трафик traefik ↔ socket-proxy. |
|
||||
|
||||
Внутри проекта соседние контейнеры обычно присоединяются к сетевому пространству имён якоря (`network_mode: service:<anchor>`) вместо самостоятельного подключения к сетям.
|
||||
|
||||
## Наслоение окружения {#env-layering}
|
||||
|
||||
Значения достигают композиции из трёх слоёв в порядке возрастания приоритета:
|
||||
|
||||
1. **`.env`** — незакоммиченные секреты, символьная ссылка в хранилище уровня 1 (учётные данные `APEX_RESTIC_*`, токены `APEX_TRAEFIK_*`, блок UID/GID).
|
||||
2. **`apex.env`** — закоммиченные несекретные скалярные значения (`APEX_TRAEFIK_DIRECT_IP`, `APEX_RESTIC_REPOSITORY`, `COMPOSE_PROFILES`, опциональный `APEX_RESTIC_COMPRESSION`).
|
||||
3. **Экспорты движка** — идентичность `APEX_*` и пути [уровней](/apex/glossary#tier) из `ctx.vars()`, внедряемые в окружение процесса действиями оркестрации и потому выигрывающие у обоих файлов.
|
||||
|
||||
Критические переменные несут маркеры `?error` в общем файле, поэтому отсутствующий секрет громко проваливает загрузку конфигурации композиции вместо запуска неправильно настроенного контейнера.
|
||||
|
||||
## Оркестрация {#orchestration}
|
||||
|
||||
`apex compose <action>` управляет каждой композицией на узле:
|
||||
|
||||
- **Порядок** — сначала ядровой проект (он владеет сетями), затем каждая сервисная композиция в алфавитном порядке; `down` разворачивает последовательность.
|
||||
- **Env-файлы** — для каждого проекта `.env` и `apex.env` передаются как `--env-file`, если они присутствуют.
|
||||
- **`up`** подразумевает `-d`; любые дополнительные аргументы передаются в `docker compose`.
|
||||
- **`--dry-run`** печатает каждую команду с её рабочим каталогом вместо выполнения — принимается в любом месте командной строки.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Схема адресации флота](/apex/concepts/addressing)
|
||||
- [Руководство: Добавление сервисной композиции](/apex/guides/service-composition)
|
||||
- [Справочник: Переменные окружения](/apex/reference/environment)
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Контекст выполнения
|
||||
description: ctx — единый шов между действием и хостом.
|
||||
weight: 14
|
||||
---
|
||||
|
||||
Каждое [действие](/apex/glossary#action) получает объект `ctx` в качестве первого аргумента. Это единственный санкционированный способ взаимодействовать с хостом: логирование, выполнение процессов, рендеринг шаблонов, изменение файлов, управление фаерволом и сервисами, а также уведомления — всё проходит через него. Поэтому действия остаются декларативными, а поведение, которое должно быть согласованным по всему флоту (формат логов, дисциплина резервного копирования, полезная нагрузка уведомлений), находится в одном месте — [движке](/apex/glossary#engine).
|
||||
|
||||
## Члены {#members}
|
||||
|
||||
| Член | Назначение |
|
||||
|---|---|
|
||||
| `ctx.log` | Логирование с префиксом: `[<node>] [<action>] [LEVEL] message`; ошибки идут в stderr, всё остальное — в stdout. |
|
||||
| `ctx.sys` | Выполнение процессов (`run`, `sudo`, `ok`), помощники для сервисов systemd и идемпотентная установка пакетов. |
|
||||
| `ctx.tpl` | Рендеринг шаблонов над опорой `configs/` с помощью `string.Template.safe_substitute`. |
|
||||
| `ctx.host` | Идемпотентное изменение хоста: резервные копии с меткой времени, атомарная запись файлов, инъекция блоков-маркеров, установка crontab с откатом. |
|
||||
| `ctx.ufw` | Применение фаервола хоста и контейнеров (см. [Концепция: Модель безопасности](/apex/concepts/security-model)). |
|
||||
| `ctx.crowdsec` | Установка CrowdSec, развёртывание конфигурации и управление коллекциями. |
|
||||
| `ctx.systemd` | Рендеринг и включение systemd-юнитов из `configs/systemd/`. |
|
||||
| `ctx.notify` | Уведомления Telegram; каждый метод возвращает булево значение, чтобы действия могли ставить код выхода в зависимость от доставки. |
|
||||
| `ctx.paths` | Разрешённые пути репозитория: `repo_root`, `commons`, `proprietaries`, `configs`, `compositions`, `core` и свойства уровней. |
|
||||
| `ctx.node` | Разрешённая запись [идентичности узла](/apex/glossary#node-identity) (`name`, `subnet`, `fqdn`, `host`); `None` в автономном чекауте commons. |
|
||||
| `ctx.commons` | Доступен только в переопределяющем проприетарном действии: `ctx.commons.run(args)` делегирует затенённой реализации commons. |
|
||||
|
||||
Сигнатуры уровня методов перечислены в [Справочник: API движка](/apex/reference/engine-api).
|
||||
|
||||
## Семантика шаблонов {#templates}
|
||||
|
||||
`ctx.tpl` рендерит файлы из `configs/` с помощью питоновского `string.Template.safe_substitute`:
|
||||
|
||||
- Известные ссылки `$VAR` и `${VAR}` заменяются.
|
||||
- **Неизвестные ссылки остаются буквальными.** Опечатка в шаблоне сохраняется в отрендеренном выводе, где она видна, вместо того чтобы молча свернуться в пустую строку.
|
||||
- Подстановка команд shell (`$(...)`) проходит нетронутой, что сохраняет целостность записей crontab со встроенным `$(cat ...)`.
|
||||
|
||||
Набор подстановки — это `ctx.vars()`: полное окружение процесса плюс предоставляемые движком значения `APEX_*`, приведённые ниже. Циклы и условия никогда не живут в шаблонах; их место — в действии.
|
||||
|
||||
## Экспорт переменных {#vars}
|
||||
|
||||
`ctx.vars()` возвращает окружение, используемое для каждого рендеринга шаблона, а его подмножество `APEX_*` экспортируется в `docker compose` действиями оркестрации. На узле оно содержит:
|
||||
|
||||
| Переменная | Значение |
|
||||
|---|---|
|
||||
| `APEX_COMMONS` | Абсолютный путь директории commons (лаунчер находится в `$APEX_COMMONS/apex`). |
|
||||
| `APEX_REPO_ROOT` | Абсолютный путь корня репозитория узла. |
|
||||
| `APEX_NODE_HOST`, `APEX_NODE_FQDN`, `APEX_SUBNET` | Разрешённые поля идентичности. |
|
||||
| `APEX_TIER1`…`APEX_TIER3` | Директории данных базового проекта по каждому [уровню](/apex/glossary#tier). |
|
||||
| `APEX_TIER1_SHARED`…`APEX_TIER3_SHARED` | Общие области внутри каждого уровня. |
|
||||
| `APEX_TIER_ROOT1`…`APEX_TIER_ROOT3` | Корни хранилищ уровней (ссылки уровня узла). |
|
||||
|
||||
Полная матрица источников/потребителей приведена в [Справочник: Переменные окружения](/apex/reference/environment).
|
||||
|
||||
## Дисциплина изменения хоста {#host}
|
||||
|
||||
`ctx.host` кодирует правила записи фреймворка:
|
||||
|
||||
- **Атомарная запись.** `write_file` рендерит во временный файл (режим `0644`, соответствующий тому, что создал бы редирект root-шелла) и устанавливает его через `mv` или `sudo cp`; перезапись существующего файла сохраняет режим этого файла.
|
||||
- **Резервные копии с меткой времени.** По запросу предыдущий файл сохраняется рядом с целевым как `<name>.<epoch>.bak` перед записью.
|
||||
- **Инъекция блоков-маркеров.** Управляемые блоки внутри системных файлов (например, `/etc/ufw/before.rules`) ограничиваются начальными/конечными комментариями-маркерами. Инъекция удаляет каждый предыдущий управляемый блок — включая блоки, оставленные предшествующим инструментарием, — перед вставкой свежего после его якорной строки, поэтому повторные запуски никогда не накапливаются.
|
||||
- **Откат crontab.** `install_crontab` делает снимок текущего crontab, отказывается от пустого рендера (код выхода `65`), показывает предпросмотр результата и восстанавливает снимок, если установка не удалась.
|
||||
|
||||
## Уведомления {#notifications}
|
||||
|
||||
`ctx.notify` отправляет сообщения Telegram через стандартную библиотеку (`urllib`). Полезная нагрузка несёт имя инстанса — публичный хост узла `APEX_NODE_HOST` — статус (`firing`/`resolved`), уровень эскалации и сообщение, усечённое до 200 символов. Каждый метод возвращает `True` только тогда, когда отправка удалась; автоматические действия трактуют неудачную отправку своего финального сообщения о статусе как сбой (код выхода `1`), поэтому сломанный канал уведомлений замечается, а не молча терпится.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Справочник: API движка](/apex/reference/engine-api)
|
||||
- [Справочник: Переменные окружения](/apex/reference/environment)
|
||||
- [Концепция: Действия и оверлей](/apex/concepts/actions-and-overlay)
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
title: "Разрешение идентичности узла"
|
||||
description: "Как узел узнаёт, кто он: ключи идентичности node.env и запасной вариант FQDN."
|
||||
weight: 12
|
||||
---
|
||||
|
||||
Каждая операция apex выполняется в контексте ровно одного [узла](/apex/glossary#node). [Движок](/apex/glossary#engine) выводит эту [идентичность узла](/apex/glossary#node-identity) — имя узла, подсеть, FQDN и публичный хост — из файла `node.env` в корне репозитория, используя имя хоста ОС только как запасной вариант для FQDN. Фреймворк не предполагает ни домена, ни кластеризации: узел объявляет, кто он, явно. Этот шаг разрешения приходит на смену механизму `root_require_node` bash-фреймворка commons «root», который заменяет apex.
|
||||
|
||||
## Источники идентичности {#sources}
|
||||
|
||||
### node.env
|
||||
|
||||
`node.env` — это обычный файл вида `KEY=value` (пригодный для `source` в shell), расположенный в корне репозитория. При разборе комментарии и пустые строки игнорируются, а окружающие значения одинарные или двойные кавычки удаляются.
|
||||
|
||||
Он объявляет идентичность явно через следующие ключи:
|
||||
|
||||
- `APEX_NODE_FQDN` — реальный FQDN узла, используемый для маршрутизации traefik `Host()`. Если не задан, движок откатывается к имени хоста ОС (и выводит предупреждение).
|
||||
- `APEX_NODE_HOST` — публичное имя хоста узла, используемое для `--host` резервного копирования и `instance` для наблюдаемости/уведомлений. По умолчанию равно `APEX_NODE_FQDN`.
|
||||
- `APEX_SUBNET` — `/24` узла для сети `direct`. **Обязателен**; другого источника для него нет (см. [Концепция: Схема адресации флота](/apex/concepts/addressing)).
|
||||
|
||||
```ini {filename="node.env"}
|
||||
APEX_NODE_FQDN=node1.example.com
|
||||
APEX_NODE_HOST=node1.example.com
|
||||
APEX_SUBNET=198.18.16.0/24
|
||||
```
|
||||
|
||||
### Имя хоста ОС
|
||||
|
||||
Имя хоста ОС используется только как запасной вариант для `APEX_NODE_FQDN`. Когда в `node.env` не задан `APEX_NODE_FQDN`, движок использует имя хоста и выводит предупреждение. Оно никогда не разбирается для получения имени узла или любой другой координаты.
|
||||
|
||||
## Порядок разрешения {#resolution}
|
||||
|
||||
{{% steps %}}
|
||||
|
||||
### Разрешите FQDN
|
||||
|
||||
`fqdn` — это `APEX_NODE_FQDN` из `node.env` или имя хоста ОС, когда этот ключ не задан. Если ни то ни другое не даёт значения, разрешение завершается неудачей (см. [Режимы сбоев](#failures)).
|
||||
|
||||
### Разрешите публичный хост
|
||||
|
||||
`host` — это `APEX_NODE_HOST` из `node.env` или `fqdn`, когда этот ключ не задан.
|
||||
|
||||
### Выведите короткое имя
|
||||
|
||||
`name` — это **первая метка** `host` (`host.split(".")[0]`) — например, `node1` для `node1.example.com`. Оно используется для логов, ветки `sync/<node>` и сообщений.
|
||||
|
||||
### Прочитайте подсеть
|
||||
|
||||
`subnet` всегда читается из `APEX_SUBNET` в `node.env`. Она обязательна.
|
||||
|
||||
{{% /steps %}}
|
||||
|
||||
Результат — запись `Node` с четырьмя полями: `name`, `subnet`, `fqdn` и `host`.
|
||||
|
||||
## Предупреждения {#warnings}
|
||||
|
||||
Разрешение идентичности выводит ровно одно предупреждение; оно сигнализирует об исправимой несогласованности, которую следует устранить.
|
||||
|
||||
| Условие | Сообщение предупреждения |
|
||||
|---|---|
|
||||
| `APEX_NODE_FQDN` не задан в `node.env` (вместо него используется имя хоста) | `APEX_NODE_FQDN not set in node.env; using hostname '<fqdn>'.` |
|
||||
|
||||
## Режимы сбоев {#failures}
|
||||
|
||||
Разрешение отказывается угадывать. Два условия фатальны, и оба завершаются с кодом `66` — соглашение движка о коде выхода для отсутствующего ввода:
|
||||
|
||||
- **Нет FQDN вовсе.** `APEX_NODE_FQDN` не задан в `node.env` *и* имя хоста ОС пусто. Движок пишет `identity: APEX_NODE_FQDN missing from node.env and no hostname` в stderr и завершается.
|
||||
- **Нет подсети.** `APEX_SUBNET` отсутствует в `node.env` (в том числе когда сам файл отсутствует). Движок пишет `identity: APEX_SUBNET missing from node.env` в stderr и завершается.
|
||||
|
||||
> [!WARNING]
|
||||
> Действительный `node.env`, несущий `APEX_SUBNET`, обязателен на каждом узле. `APEX_NODE_FQDN` можно опустить только тогда, когда имя хоста ОС предоставляет пригодный FQDN.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Модель безопасности
|
||||
description: Изоляция привилегий, управление межсетевым экраном, обнаружение вторжений и обработка секретов.
|
||||
weight: 18
|
||||
---
|
||||
|
||||
apex укрепляет [узел](/apex/glossary#node) в независимых слоях: изоляция привилегий на уровне файловой системы, управляемый межсетевой экран хоста, обнаружение вторжений, ограничение на уровне контейнеров и строгая граница секретов. Каждый слой применяется [действием](/apex/glossary#action) и потому воспроизводим и повторно запускаем.
|
||||
|
||||
## Изоляция привилегий {#privilege}
|
||||
|
||||
Каждый проект выполняется под собственным заблокированным системным пользователем `noroot-<project>`; общее хранилище доступно на запись группе с установленным sticky-битом, поэтому проекты могут обмениваться файлами, не имея возможности удалять файлы друг друга. Полный механизм — пользователи, группы и матрица владения — описан в [Концепция: Многоуровневое хранилище](/apex/concepts/tiered-storage).
|
||||
|
||||
## Межсетевой экран хоста {#firewall}
|
||||
|
||||
`apex configure/ufw` применяет межсетевой экран узла из опоры `configs/ufw/` в трёх частях:
|
||||
|
||||
- **Правила хоста** (`configs/ufw/host/host.rules`) — простые команды `ufw`, по одной на строку, применяемые по порядку.
|
||||
- **Блоки правил уровня ядра** (`configs/ufw/host/before.rules`, `after.rules`) — внедряются в `/etc/ufw/before.rules` и `/etc/ufw/after.rules` как управляемые блоки-маркеры. Внедрение является **самовосстанавливающимся**: каждый предыдущий управляемый блок — включая блоки, оставленные инструментарием-предшественником, — удаляется перед вставкой свежего блока после якоря ufw `# End required lines`, а рядом сохраняется `.bak` целевого файла с меткой времени.
|
||||
- **Правила контейнеров** (`configs/ufw/docker/*.rules`) — применяются через `ufw-docker`, который транслирует их в правила, переживающие собственные манипуляции Docker с iptables. `configure/base` устанавливает вспомогательный инструмент в `~/.local/bin/ufw-docker`.
|
||||
|
||||
Отсутствующая конфигурация — это ошибка, а не тихий пропуск: отсутствующий каталог `docker/` или `host/` завершается с кодом `66`, если существует его аналог, а отсутствующий целевой файл `/etc/ufw` прерывает запуск. Действие завершается напоминанием, что `ufw` должен быть перезапущен, чтобы изменения вступили в силу, — и что SSH-порт должен быть разрешён в первую очередь.
|
||||
|
||||
## Обнаружение вторжений {#crowdsec}
|
||||
|
||||
`apex configure/crowdsec` устанавливает CrowdSec через его официальный установщик, когда `cscli` отсутствует, рендерит всё из `configs/crowdsec/` в `/etc/crowdsec/` (источники сбора, парсеры, конфигурация bouncer) и устанавливает коллекции `crowdsecurity/linux` и `crowdsecurity/traefik`. Когда конфигурации узла включают конфигурацию firewall-bouncer, устанавливается и соответствующий пакет bouncer, замыкая цикл от обнаружения к блокировке.
|
||||
|
||||
## Ограничение на уровне контейнеров {#containers}
|
||||
|
||||
[Ядровая композиция](/apex/glossary#core-composition) кодирует укрепление на уровне контейнеров:
|
||||
|
||||
- Граничный прокси никогда не обращается к Docker-сокету: он обнаруживает бэкенды через **отфильтрованный socket-proxy**, который выставляет состояние контейнеров/сервисов в режиме только для чтения в изолированной сети.
|
||||
- `enclave` и `socket` — сети с `internal: true` — без исходящего трафика, без выставления хостовых портов.
|
||||
- Ядровые сервисы устанавливают `no-new-privileges` и работают под noroot-идентичностью проекта везде, где это позволяет нагрузка; capabilities выдаются для каждого сервиса, а не глобально.
|
||||
|
||||
## Наблюдаемость {#observability}
|
||||
|
||||
Наблюдаемость — это **исходящий push**, а не выставленный маршрут. Опциональный ядровой сервис `alloy` (Grafana Alloy, один агент на узел) собирает метрики хоста (эквивалент `node_exporter`), метрики контейнеров (cadvisor), systemd journal и логи docker-контейнеров и проталкивает их в **центральные** Prometheus и Loki, которые располагаются на выделенном узле — метрики через `prometheus.remote_write` в `APEX_PROM_PUSH_URL`, логи через `loki.write` в `APEX_LOKI_PUSH_URL`. Обе цели аутентифицируются через HTTP basic auth с использованием `APEX_OBS_BASIC_AUTH_USER` / `APEX_OBS_BASIC_AUTH_PASS`. Поскольку push является исходящим, Alloy подключается к сети `direct` (у которой есть исходящий трафик), а не к `internal`-сети `enclave`; узло-локального хранилища логов нет, как нет и маршрута `/loki`, который нужно было бы защищать.
|
||||
|
||||
## Граница секретов {#secrets}
|
||||
|
||||
- Секреты живут только в файлах `.env` для каждого проекта, которые являются **символьными ссылками в хранилище уровня 1** — за пределами рабочего дерева репозитория и покрытыми `.gitignore` (`**/.env`).
|
||||
- Каждый репозиторий узла документирует ожидаемые ключи в `SECRETS.md`; файл документирует имена, но никогда — значения.
|
||||
- Закоммиченная конфигурация (`apex.env`) ограничена несекретными скалярными значениями.
|
||||
- Общая композиция помечает критические секреты через `?error`, поэтому отсутствующее значение быстро проваливается на этапе загрузки конфигурации compose.
|
||||
- Коммиты «capture-up» безопасны по построению: игнорируемые ссылки `.env` и данные уровней никогда не попадают в git-индекс.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Многоуровневое хранилище и изоляция привилегий](/apex/concepts/tiered-storage)
|
||||
- [Концепция: Композиции и общее ядро](/apex/concepts/compositions)
|
||||
- [Справочник: Действия](/apex/reference/actions)
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Многоуровневое хранилище и изоляция привилегий
|
||||
description: Структура ссылок @tier, общее хранилище и понодовые noroot-пользователи.
|
||||
weight: 16
|
||||
---
|
||||
|
||||
Данные сервисов никогда не живут внутри репозитория [узла](/apex/glossary#node). Они живут в трёх [уровнях](/apex/glossary#tier) хранилища — раздельных расположениях, выбираемых для каждого хоста, — и достигают [композиций](/apex/glossary#compositions) через стандартизированную структуру символьных ссылок. Владение этими данными делегируется системным пользователям для каждого проекта, поэтому ни один сервисный контейнер не пишет от имени root.
|
||||
|
||||
## Структура ссылок уровней {#links}
|
||||
|
||||
`apex tiers/link <tier1> <tier2> <tier3>` строит два слоя ссылок:
|
||||
|
||||
- **На уровне узла:** `compositions/@tierN` → корень хранилища уровня. Именно их движок выставляет как `APEX_TIER_ROOT1..3`.
|
||||
- **Для каждого проекта:** для каждого `compositions/<project>/` действие создаёт `<tier-root>/<project>/` и связывает `compositions/<project>/@tierN` → `<tier-root>/<project>`. Внутри каждого уровня оно также поддерживает `shared/` с подкаталогом для каждого проекта и относительной ссылкой `shared` внутри каталога уровня каждого проекта, указывающей обратно на него.
|
||||
|
||||
Каждый проект дополнительно получает `compositions/<project>/.env` → `<tier1-root>/<project>/.env` (создаётся пустым, если отсутствует) — файл незакоммиченных секретов, описанный в [Концепция: Композиции](/apex/concepts/compositions). Ссылки `@tierN` и файлы `.env` игнорируются git; репозиторий остаётся чистым источником.
|
||||
|
||||
Если путь, который должен быть символьной ссылкой, существует как настоящий файл или каталог, действие предупреждает и запрашивает подтверждение перед его удалением; отказ (включая неинтерактивный EOF) пропускает эту ссылку и продолжает.
|
||||
|
||||
## Пользователи и группы {#users}
|
||||
|
||||
`apex tiers/useradd` провизионирует по одному заблокированному системному пользователю на проект — `noroot-<project>` (имя санитизируется, собственная первичная группа) — плюс общефлотовую группу `noroot-shared`, к которой присоединяется каждый пользователь проекта. Затем оно внедряет разрешённые ID в `.env` проекта между комментариями-маркерами, заменяя любой предыдущий блок при повторных запусках:
|
||||
|
||||
```ini {filename="compositions/<project>/.env"}
|
||||
# START AUTO GENERATED APEX-NOROOT-USERS
|
||||
APEX_UID=998
|
||||
APEX_GID=998
|
||||
APEX_SHARED_GID=997
|
||||
# END AUTO GENERATED APEX-NOROOT-USERS
|
||||
```
|
||||
|
||||
Композиции потребляют их как `user: "${APEX_UID?error}:${APEX_GID?error}"` и `group_add: ["${APEX_SHARED_GID?error}"]`.
|
||||
|
||||
## Матрица владения {#ownership}
|
||||
|
||||
`apex tiers/chown` обеспечивает владение по всем трём уровням. Без аргументов оно работает с корнями уровней (`APEX_TIER_ROOT1..3`); явные пути уровней могут переопределить это.
|
||||
|
||||
| Путь | Владение | Примечания |
|
||||
|---|---|---|
|
||||
| `<tier-root>` | `root:root` | Только inode каталога. |
|
||||
| `<tier-root>/shared` | `root:root` | Только inode каталога — никогда рекурсивно, поэтому существующие данные проектов внутри не затрагиваются. |
|
||||
| `<tier-root>/node-infra` | `root:root` | Рекурсивно. |
|
||||
| `<tier-root>/<project>` | `uid:gid` | Рекурсивно; ID читаются из `.env` проекта. |
|
||||
| `<tier-root>/shared/<project>` | `uid:shared_gid` | Рекурсивно, плюс sticky-бит — участники могут добавлять файлы, но удалять только свои. |
|
||||
|
||||
Проекты с неполным набором `APEX_UID`/`APEX_GID`/`APEX_SHARED_GID` пропускаются с предупреждением, указывающим на `tiers/useradd`.
|
||||
|
||||
## Переменные уровней движка {#variables}
|
||||
|
||||
| Переменная | Разрешается в |
|
||||
|---|---|
|
||||
| `APEX_TIER_ROOT1..3` | Корни хранилищ уровней (`compositions/@tierN`, полностью разрешённые). |
|
||||
| `APEX_TIER1..3` | Каталоги данных ядрового проекта внутри каждого уровня (`compositions/apex/@tierN`). |
|
||||
| `APEX_TIER1..3_SHARED` | Общая область внутри уровней ядрового проекта. |
|
||||
|
||||
Ядровая композиция монтирует исключительно через них; исполнитель резервного копирования вместо этого монтирует корень данных флота `/srv` в режиме только для чтения и разрешает контейнерные монтирования каждого помеченного сервиса обратно в их хостовые пути.
|
||||
|
||||
## Резервные копии {#backups}
|
||||
|
||||
`apex backup/run <telegram-bot-url>` управляет ядровым сервисом `resticontainer` (профиль `manual`, одноразовый) от начала до конца. Намерение резервного копирования **управляется метками**: resticontainer обнаруживает каждый сервис, несущий `restic.enable=true`, читает его `restic.backup.paths` (и опциональные `restic.backup.stop` / `restic.hooks.*`), разрешает эти контейнерные пути в хостовые пути и делает снимок их объединения в одном запуске restic.
|
||||
|
||||
1. Проверяет доступность репозитория через `snapshots`; инициализирует репозиторий при необходимости.
|
||||
2. Выполняет `backup` с `--host <APEX_NODE_HOST>` и `--tag biweekly` — resticontainer выполняет pre-hook каждого помеченного сервиса, останавливает любой контейнер, помеченный `restic.backup.stop`, делает снимок разрешённых хостовых путей, затем перезапускает и выполняет post-hooks. Запуск отказывается сообщать об успехе, если не появляется новый снимок (защита от обнаружения нулевого количества меток).
|
||||
3. Применяет удержание через `forget --prune` (7 ежедневных, 4 еженедельных, 12 ежемесячных).
|
||||
4. Отправляет статус в Telegram в любом случае; успех, уведомление о котором не может быть доставлено, всё равно завершается с кодом `1`, чтобы планировщики это заметили.
|
||||
|
||||
Расположение репозитория и учётные данные полностью берутся из конфигурации `APEX_RESTIC_*` (см. [Справочник: Переменные окружения](/apex/reference/environment)). Какие данные захватываются, решается для каждого сервиса его метками `restic.*` — например, ядровые сервисы резервируют только свою конфигурацию (динамические правила traefik, конфигурацию xray/adguard), оставляя воспроизводимые данные уровня 3, такие как кэши и логи, вне набора.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Модель безопасности](/apex/concepts/security-model)
|
||||
- [Справочник: Действия](/apex/reference/actions)
|
||||
- [Руководство: Добавление сервисной композиции](/apex/guides/service-composition)
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
title: Начало работы с узлом apex
|
||||
description: Начальная настройка репозитория узла и перевод хоста под управление apex.
|
||||
weight: 5
|
||||
---
|
||||
|
||||
Это краткое руководство проведёт новый хост от склонированного репозитория [узла](/apex/glossary#node) до работающего стека сервисов. Предполагается, что репозиторий узла уже существует — чтобы собрать его с нуля, см. [Руководство: Сборка репозитория узла](/apex/guides/node-repository).
|
||||
|
||||
## Предварительные требования
|
||||
|
||||
- Хост на базе Debian. Подойдёт любой FQDN — [идентичность узла](/apex/glossary#node-identity) объявляется явно в `node.env` (`APEX_NODE_FQDN`, `APEX_NODE_HOST`, `APEX_SUBNET`), а не выводится из обязательного формата имени хоста.
|
||||
- `git` и Python 3 — [движок](/apex/glossary#engine) использует только стандартную библиотеку; `pip` никогда не требуется.
|
||||
- Docker с Compose версии v2.20 или новее ([базовая композиция](/apex/glossary#core-composition) подключается через директиву `include:`).
|
||||
- Пользователь для входа с возможностью sudo.
|
||||
- Три места хранения для [уровней](/apex/glossary#tier) (например, три точки монтирования).
|
||||
|
||||
## Начальная настройка
|
||||
|
||||
{{% steps %}}
|
||||
|
||||
### Склонируйте репозиторий узла
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules git@github.com:<owner>/<node-repo>.git
|
||||
cd <node-repo>
|
||||
./init.sh
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
`init.sh` инициализирует субмодуль `commons/`, указывает `core.hooksPath` на `commons/githooks`, помечает чекаут как `safe.directory` и добавляет `commons/` в начало `PATH` — лаунчер находится в `commons/apex`.
|
||||
|
||||
### Проверьте обнаружение действий
|
||||
|
||||
```bash
|
||||
apex --help
|
||||
```
|
||||
|
||||
В списке отображается каждое действие [commons](/apex/glossary#commons) плюс любые локальные [проприетарные модули](/apex/glossary#proprietaries) узла с меткой `(local)`. Когда в `node.env` отсутствует `APEX_NODE_FQDN`, движок выводит предупреждение и откатывается к имени хоста ОС — безобидно на рабочей станции, но на самом узле задайте `APEX_NODE_FQDN` явно. (`APEX_SUBNET` обязателен: если он отсутствует, движок завершается с кодом `66`.)
|
||||
|
||||
### Свяжите уровни хранилища
|
||||
|
||||
```bash
|
||||
apex tiers/link <tier1-path> <tier2-path> <tier3-path>
|
||||
```
|
||||
|
||||
Создаёт ссылки `compositions/@tierN` уровня узла, директории уровней и ссылки `@tierN` для каждого проекта, области `shared/` и символьную ссылку `.env` каждого проекта в уровень 1. Успех завершается сообщением `@tier setup completed.`
|
||||
|
||||
### Заведите пользователей проектов
|
||||
|
||||
```bash
|
||||
apex tiers/useradd
|
||||
apex tiers/chown
|
||||
```
|
||||
|
||||
`tiers/useradd` создаёт по одному системному пользователю `noroot-<project>` на каждую композицию плюс группу `noroot-shared`, а также добавляет `APEX_UID`/`APEX_GID`/`APEX_SHARED_GID` в `.env` каждого проекта. Затем `tiers/chown` применяет матрицу владения (см. [Концепция: Многоуровневое хранилище](/apex/concepts/tiered-storage)) и завершается сообщением `Permissions applied.`
|
||||
|
||||
### Заполните базовые секреты
|
||||
|
||||
Отредактируйте `compositions/apex/.env` (символьная ссылка в хранилище уровня 1, никогда не коммитится), указав ключи, задокументированные в файле `SECRETS.md` репозитория узла:
|
||||
|
||||
```ini {filename="compositions/apex/.env"}
|
||||
APEX_RESTIC_PASSWORD=...
|
||||
APEX_RESTIC_AWS_ACCESS_KEY_ID=...
|
||||
APEX_RESTIC_AWS_SECRET_ACCESS_KEY=...
|
||||
APEX_TRAEFIK_CF_DNS_API_TOKEN=...
|
||||
APEX_TRAEFIK_ACME_EMAIL=...
|
||||
APEX_OBS_BASIC_AUTH_PASS=... # только когда включён профиль наблюдаемости alloy
|
||||
```
|
||||
|
||||
`APEX_UID`/`APEX_GID`/`APEX_SHARED_GID` уже присутствуют — `tiers/useradd` поддерживает их между комментариями-маркерами. Несекретные скалярные значения (`APEX_RESTIC_REPOSITORY`, push-URL-ы, `COMPOSE_PROFILES`, `TZ`, …) хранятся в коммитируемом `compositions/apex/apex.env` — см. [Руководство: Сборка репозитория узла](/apex/guides/node-repository).
|
||||
|
||||
### Настройте хост
|
||||
|
||||
```bash
|
||||
apex configure
|
||||
```
|
||||
|
||||
Запускает каждое действие `configure/*` в фиксированном порядке: `base`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing`. Каждый шаг можно вызвать и по отдельности; некоторые принимают `--dry-run`. См. [Руководство: Эксплуатация узла](/apex/guides/host-operations).
|
||||
|
||||
### Поднимите стек
|
||||
|
||||
```bash
|
||||
apex compose up
|
||||
```
|
||||
|
||||
Сначала поднимается базовая композиция (она владеет сетями), затем каждая композиция сервиса в алфавитном порядке.
|
||||
|
||||
{{% /steps %}}
|
||||
|
||||
## Проверка
|
||||
|
||||
- `apex --help` выводит список действий без предупреждений на работающем хосте.
|
||||
- `docker ps` показывает базовые контейнеры (`apex-traefik`, `apex-socket-proxy` плюс любые сервисы, включённые профилями) и якорь каждого проекта.
|
||||
- `apex utils/lint-docker-compose` сообщает `Anchor IP validation passed.`
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Руководство: Сборка репозитория узла](/apex/guides/node-repository)
|
||||
- [Концепция: Архитектура фреймворка](/apex/concepts/architecture)
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Глоссарий
|
||||
description: Терминология, используемая в рамках фреймворка операций флота apex.
|
||||
weight: 100
|
||||
---
|
||||
|
||||
Терминология, используемая в рамках фреймворка операций флота apex.
|
||||
|
||||
## Action {#action}
|
||||
|
||||
**Action (Действие)** - Атомарная задача автоматизации: один модуль на Python, экспортирующий `METADATA = Meta(...)` и `run(ctx, args)`, диспетчеризуемый по пути своего файла как `apex <group>/<action>`.
|
||||
|
||||
## Anchor IP {#anchor-ip}
|
||||
|
||||
**Anchor IP (Якорный IP)** - Статический `ipv4_address`, который основной контейнер проекта удерживает в сети `direct`. Якоря делают адреса сервисов предсказуемыми и проверяются линтером compose.
|
||||
|
||||
## Capture-up {#capture-up}
|
||||
|
||||
**Capture-up** - «capture-up» (запись состояния хоста в ветку `sync/<node>`): поток, который фиксирует текущее состояние хоста обратно в его репозиторий — плановый коммит в ветку `sync/<node>`, ребазируемый на `main` и сворачиваемый обратно средствами CI. Дрейф становится diff-ом, а не расхождением.
|
||||
|
||||
## Commons {#commons}
|
||||
|
||||
**Commons** - «commons» (общий репозиторий) (`apex.ermnvldmr.com`), содержащий движок, общие действия и базовую композицию. Каждый узел подключает его как закреплённый git-субмодуль `commons/`.
|
||||
|
||||
## Compositions {#compositions}
|
||||
|
||||
**Compositions (Композиции)** - Опора репозитория узла, объявляющая среду выполнения узла: по одному docker-compose-проекту на сервис плюс базовая обёртка и реестр адресации.
|
||||
|
||||
## Configs {#configs}
|
||||
|
||||
**Configs (Конфигурации)** - Опора репозитория узла, содержащая исходники конфигурации хоста (`ufw/`, `cron/`, `systemd/`, `crowdsec/`), рендеримые через слой шаблонов действиями `configure/*`.
|
||||
|
||||
## Core composition {#core-composition}
|
||||
|
||||
**Core composition (Базовая композиция)** - Общий, параметризуемый через `${APEX_*}` docker-compose-проект, определяющий общие сервисы флота (краевой прокси, socket-прокси и опциональные сервисы профилей). Определяется один раз в commons, включается на каждом узле.
|
||||
|
||||
## Enclave {#enclave}
|
||||
|
||||
**Enclave (Анклав)** - Приватное адресное пространство флота, `198.18.0.0/16`, и, как расширение, внутренняя backend-сеть обратного прокси с именем `enclave` в базовой композиции.
|
||||
|
||||
## Engine {#engine}
|
||||
|
||||
**Engine (Движок)** - Среда выполнения на stdlib-Python в `commons/engine/`: лаунчер, разрешение идентичности, оверлей, контекст выполнения и вспомогательная библиотека `ctx.*`.
|
||||
|
||||
## Node {#node}
|
||||
|
||||
**Node (Узел)** - Хост, управляемый фреймворком, определяемый ровно одним репозиторием узла и идентифицируемый по FQDN, который он объявляет в `node.env` (например, `node1.example.com`).
|
||||
|
||||
## Node identity {#node-identity}
|
||||
|
||||
**Node identity (Идентичность узла)** - Разрешённая запись о том, кто есть узел — имя, подсеть, FQDN, публичный хост — выведенная из `node.env`, с именем хоста ОС в качестве запасного варианта для FQDN.
|
||||
|
||||
## Overlay {#overlay}
|
||||
|
||||
**Overlay (Оверлей)** - Правило разрешения, позволяющее проприетарным модулям узла добавлять, переопределять, оборачивать или отключать действия commons: `proprietaries/actions/` проверяется раньше `commons/actions/`.
|
||||
|
||||
## Proprietaries {#proprietaries}
|
||||
|
||||
**Proprietaries (Проприетарные модули)** - Опора репозитория узла для локального кода узла: действий, библиотек и исходников инструментов, которые применяются ровно к одному узлу и потому не относятся к commons.
|
||||
|
||||
## Tier {#tier}
|
||||
|
||||
**Tier (Уровень)** - Один из трёх стандартизированных классов хранилища, монтируемых узлом (конфигурация, ценные данные, воспроизводимые данные), доступный из композиций через структуру символьных ссылок `@tierN`.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Руководства
|
||||
description: Практические руководства по сборке репозиториев узлов, действий и композиций.
|
||||
weight: 20
|
||||
---
|
||||
|
||||
Практические руководства для повседневной работы с apex. Каждое руководство решает одну повторяющуюся задачу: сборка репозитория [узла](/apex/glossary#node), разработка [действий](/apex/glossary#action) для [движка](/apex/glossary#engine), определение [композиций](/apex/glossary#compositions) сервисов и эксплуатация лежащего в основе хоста.
|
||||
|
||||
## Доступные руководства {#available-guides}
|
||||
|
||||
{{< cards >}}
|
||||
{{< card link="/apex/guides/node-repository" title="Репозиторий узла" subtitle="Соберите репозиторий узла, который объявляет идентичность узла, его конфигурации и композиции." >}}
|
||||
{{< card link="/apex/guides/action-development" title="Разработка действий" subtitle="Пишите действия, которые движок диспетчеризует на узле." >}}
|
||||
{{< card link="/apex/guides/service-composition" title="Композиция сервисов" subtitle="Определяйте композиции проектов и разворачивайте их вместе с базовой композицией." >}}
|
||||
{{< card link="/apex/guides/host-operations" title="Операции с хостом" subtitle="Выполняйте рутинные операции над работающим хостом через движок." >}}
|
||||
{{< /cards >}}
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Docs: Getting started](/apex/getting-started)
|
||||
- [Docs: Concepts](/apex/concepts)
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Разработка действий
|
||||
description: Пишите, переопределяйте и тестируйте действия движка в commons или proprietaries.
|
||||
weight: 22
|
||||
---
|
||||
|
||||
**Контекст:** вся автоматизация поставляется в виде [действий](/apex/glossary#action) — по одному Python-модулю на каждое, диспетчеризуемых по пути. Общее поведение размещается в [commons](/apex/glossary#commons) `actions/` («commons» — общий репозиторий); всё, что специфично для узла, размещается в `proprietaries/actions/` этого узла. Оба используют идентичный контракт, а запускатель помещает оба дерева в `sys.path`, поэтому проприетарное действие импортирует `engine.*` точно так же, как и действие из commons.
|
||||
|
||||
## Минимальное действие {#minimal}
|
||||
|
||||
```python {filename="proprietaries/actions/utils/example.py"}
|
||||
"""Однострочный docstring модуля, описывающий действие."""
|
||||
import os
|
||||
from engine.descriptor import Meta, Arg, Flag
|
||||
|
||||
METADATA = Meta(
|
||||
summary="Скопировать отрендеренную конфигурацию на место.",
|
||||
args=[Arg("target_dir", "Каталог назначения"),
|
||||
Flag("--dry-run", "Только предпросмотр.")],
|
||||
)
|
||||
|
||||
def run(ctx, args):
|
||||
if not os.path.isdir(args.target_dir):
|
||||
ctx.log.error(f"Нет такого каталога: {args.target_dir}")
|
||||
raise SystemExit(66)
|
||||
content = ctx.tpl.render("example/config", ctx.vars())
|
||||
ctx.host.write_file(os.path.join(args.target_dir, "config"), content,
|
||||
dry_run=args.dry_run, sudo=True)
|
||||
ctx.log.success("Конфигурация развёрнута.")
|
||||
```
|
||||
|
||||
Путь к файлу — это его имя: модуль выше диспетчеризуется как `apex utils/example`. Верните `0`/`None` для успеха, целое число для конкретного кода выхода или возбудите `SystemExit(code)`; [соглашение о кодах выхода](/apex/concepts/actions-and-overlay#exit-codes) таково: `64` — ошибка использования, `65` — ошибка данных, `66` — отсутствующий ввод, `1` — общая ошибка.
|
||||
|
||||
## Соглашения для действий без надзора {#unattended}
|
||||
|
||||
Действия, запускаемые из cron, следуют двум правилам, чтобы сбои приводили к оповещению, а не исчезали бесследно:
|
||||
|
||||
- **Оберните тело** в `try/except Exception` и отправьте `ctx.notify.error(...)` перед повторным возбуждением как `SystemExit(1)` — Python-эквивалент shell-ловушки ERR.
|
||||
- **Привяжите код выхода к финальному уведомлению**: `ctx.notify.*` возвращает `False` при неудачной доставке; завершённый запуск, чьё статусное сообщение так и не пришло, всё равно должен завершиться с кодом `1`.
|
||||
|
||||
```python
|
||||
try:
|
||||
...работа...
|
||||
log.success("Готово.")
|
||||
if not ctx.notify.success(TITLE, url, "Готово.", node):
|
||||
raise SystemExit(1)
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"{type(e).__name__}: {e}")
|
||||
ctx.notify.error(TITLE, url, f"Критическая ошибка: {e}", node)
|
||||
raise SystemExit(1)
|
||||
```
|
||||
|
||||
## Переопределение, обёртывание, отключение {#override}
|
||||
|
||||
Проприетарный файл с тем же относительным путём, что и действие из commons, полностью затеняет его. Внутри затеняющего модуля `ctx.commons.run(args)` вызывает затенённую реализацию — паттерн обёртывания:
|
||||
|
||||
```python {filename="proprietaries/actions/sync/packages.py"}
|
||||
from engine.descriptor import Meta, Arg
|
||||
|
||||
METADATA = Meta(summary="sync/packages с локальным для узла предварительным шагом.",
|
||||
args=[Arg("telegram_bot_url", "Telegram Bot URL")])
|
||||
|
||||
def run(ctx, args):
|
||||
ctx.log.info("Локальная для узла подготовка...")
|
||||
return ctx.commons.run(args) # делегирование реализации из commons
|
||||
```
|
||||
|
||||
Чтобы вывести действие из эксплуатации на одном узле, присвойте `DISABLED = "reason"` на верхнем уровне модуля: диспетчеризация отклонит его (код выхода `1`), а в списке отобразится `[DISABLED: reason]`.
|
||||
|
||||
## Тестирование {#testing}
|
||||
|
||||
Набор тестов commons использует stdlib `unittest`:
|
||||
|
||||
```bash
|
||||
./tests/run.sh # python3 -m unittest discover -s tests -p 'test_*.py' -v
|
||||
```
|
||||
|
||||
Изменения движка должны сохранять набор тестов «зелёным». Дешёвая smoke-проверка для любого действия — из commons или проприетарного — это его парсер: `apex <name> --help` должен завершиться с кодом `0`, что доказывает, что модуль разбирается, его `METADATA` корректно сформирован, и его видит механизм обнаружения.
|
||||
|
||||
Pre-commit-хук commons (подключаемый `init.sh` через `core.hooksPath`) запускает `apex utils/lint-docker-compose --hook` для staged-файлов compose, поэтому изменения композиций линтуются в момент коммита.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Concept: Actions and the overlay](/apex/concepts/actions-and-overlay)
|
||||
- [Concept: The execution context](/apex/concepts/execution-context)
|
||||
- [Reference: Engine API](/apex/reference/engine-api)
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Эксплуатация узла
|
||||
description: "Операции day-2: конфигурирование, композиции, резервные копии и синхронизация репозитория."
|
||||
weight: 24
|
||||
---
|
||||
|
||||
**Контекст:** после первоначальной настройки [узел](/apex/glossary#node) эксплуатируется полностью через [движок](/apex/glossary#engine). Это руководство охватывает повторяющиеся операции и то, что каждая из них фактически делает на хосте.
|
||||
|
||||
## Конфигурирование хоста {#configure}
|
||||
|
||||
`apex configure` запускает полный набор конфигурирования в фиксированном порядке; каждый шаг вызывается индивидуально, когда изменился только один слой:
|
||||
|
||||
| Шаг | Эффект |
|
||||
|---|---|
|
||||
| `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/`, затем выполняет daemon-reload и включает каждый `.service`. Поддерживает `--dry-run`. |
|
||||
| `configure/routing` | Включает IP-форвардинг и устанавливает правило и таблицу policy-routing по fwmark, используемые границей анклава; идемпотентно. |
|
||||
|
||||
После `configure/ufw` намеренно перезапустите `ufw` (`sudo systemctl restart ufw`) — действие напоминает об этом и предупреждает, что порт SSH уже должен быть разрешён.
|
||||
|
||||
## Жизненный цикл композиции {#compose}
|
||||
|
||||
```bash
|
||||
apex compose up # сначала core, затем сервисы по алфавиту
|
||||
apex compose down # обратный порядок
|
||||
apex compose restart
|
||||
apex compose up --dry-run # только напечатать команды
|
||||
```
|
||||
|
||||
Дополнительные аргументы передаются в `docker compose` для каждого проекта. Файлы `.env` и `apex.env` каждого проекта подставляются автоматически, если они присутствуют.
|
||||
|
||||
## Резервные копии {#backups}
|
||||
|
||||
```bash
|
||||
apex backup/run "$(cat <path-to-telegram-url-file>)"
|
||||
```
|
||||
|
||||
Запускает [resticontainer](https://github.com/deytenit/resticontainer) в режиме one-shot под профилем compose `manual`: init-if-needed, затем управляемое метками резервное копирование, которое определяет намерение каждого сервиса из его меток compose `restic.*` (`restic.enable`, `restic.backup.paths`, …), делает снимок объединения разрешённых путей хоста в одном запуске restic (`--host` = `APEX_NODE_HOST`, `--compression max`), применяет политику хранения (`forget --prune`) и уведомляет. Он отказывается сообщать об успехе, если ни один сервис с меткой `restic.*` не создал нового снимка. Полный процесс описан в [Concept: Tiered storage](/apex/concepts/tiered-storage#backups). Запланируйте его в `configs/cron/crontab`, чтобы запуск и его результат в Telegram проходили без надзора.
|
||||
|
||||
## Синхронизация репозитория (capture-up) {#sync}
|
||||
|
||||
```bash
|
||||
apex sync/repository <telegram-bot-url>
|
||||
```
|
||||
|
||||
Коммитит рабочую копию в `sync/<node>`, выполняет rebase на `origin/main` и force-push с lease; CI сворачивает ветку обратно в `main`. «capture-up» — механизм захвата состояния узла обратно в репозиторий. Действие отказывается выполняться — с постраничной ошибкой — когда сабмодуль commons «грязный» или дерево `compositions/` отсутствует, и аккуратно прерывается при конфликте rebase (требуется ручное вмешательство). См. [Concept: Framework architecture](/apex/concepts/architecture#capture-up) для описания модели.
|
||||
|
||||
## Осведомлённость об обновлениях {#updates}
|
||||
|
||||
```bash
|
||||
apex sync/packages <telegram-bot-url>
|
||||
```
|
||||
|
||||
Сообщает об ожидающих apt-обновлениях и устаревших образах контейнеров (локальные дайджесты сравниваются с реестром через `skopeo`) в едином дайджесте Telegram. Он ничего не изменяет на хосте — это плановая проверка осведомлённости.
|
||||
|
||||
## Извлечение сертификатов {#certs}
|
||||
|
||||
```bash
|
||||
apex utils/extract-traefik-certs <acme.json> <dest-dir> [telegram-bot-url]
|
||||
```
|
||||
|
||||
Выгружает хранилище ACME граничного прокси в пары `fullchain.pem`/`privkey.pem` по каждому домену (через контейнеризованный dumper), ограничивает права на ключи до `600` и переназначает владельца вывода вызывающему пользователю — для сервисов, которым нужен «сырой» PEM-материал вместо TLS, терминируемого прокси.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Getting started with an apex node](/apex/getting-started)
|
||||
- [Reference: Actions](/apex/reference/actions)
|
||||
- [Docs: Troubleshooting](/apex/troubleshooting)
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Сборка репозитория узла
|
||||
description: Соберите четыре опоры, зафиксируйте commons и подключите поток capture-up.
|
||||
weight: 21
|
||||
---
|
||||
|
||||
**Контекст:** каждый [узел](/apex/glossary#node) получает ровно один репозиторий. Выбирайте любое имя репозитория — [идентичность](/apex/glossary#node-identity) объявляется явно в `node.env`, поэтому имя репозитория — это соглашение, а не функциональный ввод. Это руководство собирает такой репозиторий с нуля.
|
||||
|
||||
{{% steps %}}
|
||||
|
||||
### Инициализируйте репозиторий и зафиксируйте commons
|
||||
|
||||
```bash
|
||||
git init <node-repo> && cd <node-repo>
|
||||
git submodule add <commons-url> commons
|
||||
( cd commons && git fetch --tags && git checkout <release-tag> )
|
||||
git add .gitmodules commons
|
||||
mkdir -p proprietaries/actions proprietaries/lib configs compositions/apex .github/workflows
|
||||
```
|
||||
|
||||
Gitlink фиксирует точную версию движка, которую запускает узел. Обновление в дальнейшем означает checkout более нового тега внутри `commons/` и коммит перемещённого указателя.
|
||||
|
||||
### Объявите идентичность
|
||||
|
||||
```ini {filename="node.env"}
|
||||
APEX_NODE_FQDN=node1.example.com
|
||||
APEX_NODE_HOST=node1.example.com
|
||||
APEX_SUBNET=198.18.16.0/24
|
||||
APEX_GIT_AUTHOR_NAME=apex [bot]
|
||||
APEX_GIT_AUTHOR_EMAIL=apex@localhost
|
||||
```
|
||||
|
||||
`node.env` объявляет идентичность узла явно — не предполагается никакой формы домена. `APEX_NODE_FQDN` — это реальный FQDN, используемый для маршрутизации traefik по `Host()` (fallback: имя хоста ОС, с предупреждением). `APEX_NODE_HOST` — это публичное имя хоста, используемое для `--host` резервного копирования и метки `instance` наблюдаемости/уведомлений (по умолчанию: `APEX_NODE_FQDN`); короткое `name` узла — это его первая метка. `APEX_SUBNET` — это /24 узла для сети `direct` и является **обязательным** — отсутствующее значение приводит к выходу с кодом `66` (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)). `APEX_GIT_AUTHOR_NAME`/`APEX_GIT_AUTHOR_EMAIL` — это идентичность бота capture-up (общие значения по умолчанию, если не заданы).
|
||||
|
||||
### Напишите базовую обёртку и её env-файлы
|
||||
|
||||
```yaml {filename="compositions/apex/docker-compose.yml"}
|
||||
name: apex
|
||||
include:
|
||||
- ../../commons/compositions/apex/docker-compose.yml
|
||||
```
|
||||
|
||||
```ini {filename="compositions/apex/apex.env"}
|
||||
APEX_TRAEFIK_DIRECT_IP=198.18.16.10
|
||||
APEX_RESTIC_REPOSITORY=s3:<endpoint>/<bucket>/<node>
|
||||
COMPOSE_PROFILES=adguard,xray,alloy
|
||||
# цели push-наблюдаемости (только когда включён профиль alloy)
|
||||
APEX_PROM_PUSH_URL=https://<central-host>/api/v1/push
|
||||
APEX_LOKI_PUSH_URL=https://<central-host>/loki/api/v1/push
|
||||
APEX_OBS_BASIC_AUTH_USER=<node-user>
|
||||
# опциональные скаляры со значениями compose по умолчанию:
|
||||
# TZ=Etc/UTC
|
||||
# APEX_RESTIC_COMPRESSION=max
|
||||
# APEX_RESTIC_AWS_REGION=us-east-1 # задайте для любого не-AWS S3-хранилища
|
||||
# APEX_RESTIC_AWS_ENDPOINT= # напр., URL S3-совместимого эндпоинта
|
||||
```
|
||||
|
||||
`apex.env` содержит только коммитируемые несекретные скаляры. `COMPOSE_PROFILES` выбирает, какие опциональные базовые сервисы запускает этот узел — `adguard`, `xray`, `alloy` (агент push-наблюдаемости) и `manual` (одноразовые резервные копии) — и может быть пустым. Задокументируйте некоммитируемые секретные ключи в `compositions/apex/SECRETS.md`, чтобы оператор на свежем хосте знал, что заполнить в `.env` (`APEX_RESTIC_PASSWORD`, ключи доступа S3, `APEX_TRAEFIK_CF_DNS_API_TOKEN`, `APEX_TRAEFIK_ACME_EMAIL` и `APEX_OBS_BASIC_AUTH_PASS`, когда наблюдаемость включена).
|
||||
|
||||
### Игнорируйте то, чему никогда не место в git
|
||||
|
||||
```gitignore {filename=".gitignore"}
|
||||
**/@tier1
|
||||
**/@tier2
|
||||
**/@tier3
|
||||
**/.env
|
||||
!compositions/apex/apex.env
|
||||
__pycache__/
|
||||
*.pyc
|
||||
```
|
||||
|
||||
### Добавьте bootstrap-скрипт
|
||||
|
||||
`init.sh` подготавливает свежий клон на хосте: `git submodule update --init --recursive`, `git config core.hooksPath commons/githooks`, `git config --global --add safe.directory <repo>` и добавляет `<repo>/commons` в начало `PATH` в login-shell, чтобы `apex` разрешался. Пометьте его исполняемым.
|
||||
|
||||
### Заведите реестр адресации
|
||||
|
||||
Создайте `compositions/ADDRESSING.md` с подсетью узла (`APEX_SUBNET`) и зарезервированными диапазонами (`.10`–`.19` — core, `.20`+ — сервисы). Каждый [якорный IP](/apex/glossary#anchor-ip), выделяемый позднее, записывается здесь.
|
||||
|
||||
### Подключите CI capture-up
|
||||
|
||||
Добавьте плановый workflow, который сливает `sync/<node>` в `main`, когда ветка существует, коммитя под идентичностью бота. Выполняйте checkout **без** сабмодулей — слияние лишь перемещает указатель commons, а submodule-URL по SSH не извлекается с CI-токеном по умолчанию. См. [Concept: Framework architecture](/apex/concepts/architecture) для полного цикла [capture-up](/apex/glossary#capture-up).
|
||||
|
||||
### Проверьте
|
||||
|
||||
```bash
|
||||
./commons/apex --help
|
||||
```
|
||||
|
||||
В списке должно отображаться каждое действие из commons (плюс ваши proprietaries, после их добавления). Вне хоста — до заполнения `node.env` — ожидайте безобидное предупреждение о том, что `APEX_NODE_FQDN` не задан (движок откатывается к имени хоста ОС); `APEX_SUBNET` при этом всё равно должен быть задан, иначе движок завершится с кодом `66`. Закоммитьте собранный репозиторий в `main`.
|
||||
|
||||
{{% /steps %}}
|
||||
|
||||
## Заметки
|
||||
|
||||
- Опора `proprietaries/` может начинаться пустой (сохраняйте её присутствие с помощью файлов `.gitkeep`); движок корректно обрабатывает отсутствующий оверлей.
|
||||
- Переносите источники конфигурации хоста в `configs/` (`ufw/`, `cron/crontab`, `systemd/`, `crowdsec/`) по мере того как узел принимает каждый шаг `configure/*` — действия потребляют `configs/` дословно через слой шаблонов.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Getting started with an apex node](/apex/getting-started)
|
||||
- [Concept: Framework architecture](/apex/concepts/architecture)
|
||||
- [Guide: Add a service composition](/apex/guides/service-composition)
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: Добавление композиции сервисов
|
||||
description: "Подключите новый проект к узлу: якорный IP, сети, уровни и линтинг."
|
||||
weight: 23
|
||||
---
|
||||
|
||||
**Контекст:** проект присоединяется к [узлу](/apex/glossary#node) как один каталог в `compositions/`, содержащий написанный вручную файл compose. Контракт фреймворка для этого файла невелик: статический [якорный IP](/apex/glossary#anchor-ip) в сети `direct`, ссылки на внешние сети по ключу и пути к данным относительно уровней.
|
||||
|
||||
{{% steps %}}
|
||||
|
||||
### Выделите якорный адрес
|
||||
|
||||
Выберите следующий свободный адрес `.20` или выше в /24 узла и запишите его в `compositions/ADDRESSING.md`. Адреса `.10`–`.19` зарезервированы для [базовой композиции](/apex/glossary#core-composition) (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)).
|
||||
|
||||
### Напишите файл compose
|
||||
|
||||
```yaml {filename="compositions/<project>/docker-compose.yml"}
|
||||
name: <project>
|
||||
services:
|
||||
<anchor>:
|
||||
image: ...
|
||||
container_name: <project>-<anchor>
|
||||
user: "${APEX_UID?error}:${APEX_GID?error}"
|
||||
group_add:
|
||||
- "${APEX_SHARED_GID?error}"
|
||||
volumes:
|
||||
- ./@tier1/<anchor>/config:/config
|
||||
- ./@tier2/<anchor>/data:/data
|
||||
networks:
|
||||
direct:
|
||||
ipv4_address: 198.18.16.20
|
||||
enclave: {}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.<project>.rule=Host(`...`)"
|
||||
- "traefik.http.services.<project>.loadbalancer.server.port=8080"
|
||||
# намерение резервного копирования (resticontainer, определяется из меток restic.*)
|
||||
- "restic.enable=true"
|
||||
- "restic.backup.paths=/config"
|
||||
restart: unless-stopped
|
||||
|
||||
<sidecar>:
|
||||
image: ...
|
||||
network_mode: "service:<anchor>"
|
||||
|
||||
networks:
|
||||
direct:
|
||||
external: true
|
||||
enclave:
|
||||
external: true
|
||||
```
|
||||
|
||||
Применяемые соглашения:
|
||||
|
||||
- **Один якорь на проект** держит статический `ipv4_address` в `direct`; sidecar-контейнеры разделяют его сетевое пространство имён через `network_mode: service:<anchor>`.
|
||||
- **На сети ссылаются по ключу** (`direct`, `enclave`, `socket`) с `external: true` — их определения принадлежат базовой композиции.
|
||||
- **Членство в `enclave` плюс метки traefik** публикуют сервис через граничный прокси; прокси обнаруживает его через socket-прокси, никогда через опубликованные порты.
|
||||
- **Пути к данным относительны уровням** (`./@tierN/...`) и разрешаются через симлинки, создаваемые `tiers/link`.
|
||||
- **Резервное копирование включается по выбору через метки `restic.*`.** `apex backup/run` управляет [resticontainer](https://github.com/deytenit/resticontainer), который определяет, что снимать, из меток compose: `restic.enable=true` помечает сервис, `restic.backup.paths=<container-path>` называет монтирования для резервного копирования (resticontainer разрешает их в пути хоста). Сервис без меток `restic.*` просто не резервируется. Резервируйте конфигурацию только там, где данные восстановимы.
|
||||
- Тройка `${APEX_UID...}` берётся из `.env` проекта, поддерживаемого `tiers/useradd`.
|
||||
|
||||
### Обеспечьте хранилище и идентичность
|
||||
|
||||
```bash
|
||||
apex tiers/link <tier1> <tier2> <tier3>
|
||||
apex tiers/useradd
|
||||
apex tiers/chown
|
||||
```
|
||||
|
||||
Повторный запуск тройки идемпотентен; он подхватывает новый проект, создаёт его каталоги уровней и `.env`, обеспечивает его пользователя `noroot-<project>` и применяет матрицу владения.
|
||||
|
||||
### Линтинг
|
||||
|
||||
```bash
|
||||
apex utils/lint-docker-compose
|
||||
```
|
||||
|
||||
Проверяет каждый якорный IP (корректность формы, уникальность, нахождение внутри подсети узла) и запускает линтер compose по репозиторию. Pre-commit-хук обеспечивает тот же линтинг для staged-файлов compose.
|
||||
|
||||
### Разверните
|
||||
|
||||
```bash
|
||||
apex compose up
|
||||
```
|
||||
|
||||
Базовая композиция уже поднята; новый проект стартует в своём алфавитном слоте. Проверьте с помощью `docker ps` и, если сервис проксируется, через его маршрут на границе.
|
||||
|
||||
{{% /steps %}}
|
||||
|
||||
## Заметки
|
||||
|
||||
- Внутренние секреты сервиса (например, пароль базы данных) размещаются в собственном `.env` проекта в хранилище уровня 1, рядом с автоматически внедряемым блоком UID.
|
||||
- Никогда не определяйте `direct`/`enclave`/`socket` внутри файла проекта — второе определение будет конкурировать с базовой композицией за владение сетью.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Concept: Compositions and the shared core](/apex/concepts/compositions)
|
||||
- [Concept: Tiered storage and privilege isolation](/apex/concepts/tiered-storage)
|
||||
- [Concept: Fleet addressing scheme](/apex/concepts/addressing)
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: Справочник
|
||||
description: CLI, действия, переменные окружения и API движка.
|
||||
weight: 30
|
||||
---
|
||||
|
||||
Технический справочник по поверхности фреймворка операций над флотом apex: интерфейс командной строки, встроенные [действия](/apex/glossary#action), контракт окружения, который они потребляют, и API [движка](/apex/glossary#engine) на Python.
|
||||
|
||||
{{% cards %}}
|
||||
{{< card link="cli" title="Справочник CLI" icon="terminal" subtitle="Вызов из командной строки, подкоманды и коды возврата." >}}
|
||||
{{< card link="actions" title="Справочник действий" icon="play" subtitle="Встроенные действия и их аргументы." >}}
|
||||
{{< card link="environment" title="Справочник окружения" icon="variable" subtitle="Переменные окружения, доступные действиям и композициям." >}}
|
||||
{{< card link="engine-api" title="Справочник API движка" icon="code" subtitle="Модули Python, обеспечивающие работу движка." >}}
|
||||
{{% /cards %}}
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Справочник действий
|
||||
description: "Каждое действие commons: аргументы и поведение."
|
||||
weight: 32
|
||||
---
|
||||
|
||||
Шестнадцать действий [«commons»](/apex/glossary#commons) (общего репозитория), по группам. Аргументы показаны в том виде, в котором их принимает парсер; необязательные аргументы заключены в квадратные скобки. Коды возврата следуют [соглашению движка](/apex/reference/cli#exit-codes).
|
||||
|
||||
## configure {#configure}
|
||||
|
||||
### configure
|
||||
|
||||
`apex configure [--dry-run]`
|
||||
|
||||
Запускает весь набор в фиксированном порядке — `base`, `ufw`, `crowdsec`, `cron`, `systemd`, `routing` — и останавливается на первом неудачном шаге, пробрасывая его код возврата. `--dry-run` передаётся тем шагам, которые его поддерживают.
|
||||
|
||||
### configure/base
|
||||
|
||||
`apex configure/base`
|
||||
|
||||
Проверка только для Debian (иначе выход с кодом `1`). Обновляет индекс пакетов, устанавливает необходимые пакеты (`curl`, `wget`, `git`, `rsyslog`), гарантирует, что rsyslog запущен, устанавливает `ufw`, если он отсутствует, и загружает вспомогательную утилиту `ufw-docker` в `~/.local/bin/ufw-docker` (пропускается, если она уже присутствует).
|
||||
|
||||
### configure/ufw
|
||||
|
||||
`apex configure/ufw [--dry-run]`
|
||||
|
||||
Применяет межсетевой экран узла из `configs/ufw/`: `host/host.rules` через обычный `ufw`, `host/before.rules`/`after.rules` как самовосстанавливающиеся управляемые блоки внутри `/etc/ufw/`, а `docker/*.rules` — через `ufw-docker`. Выходит с кодом `1`, когда отсутствует корень конфигурации, `66` — когда отсутствует ровно один из каталогов `docker/`/`host/`, `1` — когда целевой файл в `/etc/ufw` не существует. Требует предварительного выполнения `configure/base` (проверяет `ufw` и `ufw-docker`). Завершается напоминанием о необходимости перезапуска.
|
||||
|
||||
### configure/crowdsec
|
||||
|
||||
`apex configure/crowdsec [--dry-run]`
|
||||
|
||||
Устанавливает CrowdSec через официальный скрипт, если `cscli` отсутствует, рендерит `configs/crowdsec/` в `/etc/crowdsec/` (создавая резервные копии перезаписываемых файлов), устанавливает коллекции `crowdsecurity/linux` и `crowdsecurity/traefik`, а также устанавливает firewall-баунсер iptables, когда существует `configs/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml.local`, а пакет отсутствует. `--dry-run` рендерит без записи и пропускает шаги установки.
|
||||
|
||||
### configure/cron
|
||||
|
||||
`apex configure/cron [--dry-run]`
|
||||
|
||||
Рендерит `configs/cron/crontab` с помощью `ctx.vars()` и устанавливает его через `crontab -`. Отсутствующий исходный файл: выход `1`. Пустой результат рендеринга: выход `65`, предыдущий crontab сохраняется. Неудачная установка: предыдущий crontab восстанавливается, выход `1`. Результат рендеринга всегда выводится для проверки.
|
||||
|
||||
### configure/systemd
|
||||
|
||||
`apex configure/systemd [--dry-run]`
|
||||
|
||||
Рендерит каждый файл верхнего уровня (не скрытый) из `configs/systemd/` в `/etc/systemd/system/<name>`; для каждого юнита `.service` выполняет daemon-reload и включает его с `--now`. Отсутствующий каталог конфигурации: выход `1`.
|
||||
|
||||
### configure/routing
|
||||
|
||||
`apex configure/routing`
|
||||
|
||||
Включает `net.ipv4.ip_forward`, устанавливает правило политики fwmark `0x1` → routing-table-100 и локальный маршрут по умолчанию в этой таблице. Обе проверки идемпотентны — повторные запуски ничего не меняют.
|
||||
|
||||
## sync {#sync}
|
||||
|
||||
### sync/repository
|
||||
|
||||
`apex sync/repository <telegram_bot_url>`
|
||||
|
||||
Коммит [«capture-up»](/apex/glossary#capture-up) (захват изменений): переключается на (или создаёт) `sync/<node>`, добавляет всё дерево плюс обновлённый указатель на подмодуль commons, коммитит под идентичностью бота, если что-либо изменилось, выполняет rebase на `origin/main`, обновляет подмодули и пушит с `--force-with-lease`. Прерывается (выход `1`), когда подмодуль commons содержит незакоммиченные изменения или отсутствует `compositions/`; при конфликте rebase прерывает rebase и оповещает через Telegram. Успех и каждый путь отказа сопровождаются уведомлением; неудачная отправка уведомления об успехе также приводит к выходу с кодом `1`.
|
||||
|
||||
### sync/packages
|
||||
|
||||
`apex sync/packages <telegram_bot_url>`
|
||||
|
||||
Только для Debian. Собирает ожидающие обновления apt (`--just-print`) и устаревшие образы работающих контейнеров (локальный RepoDigest против дайджеста реестра через `skopeo`), затем отправляет одну сводку в Telegram. Только для чтения в отношении пакетов и образов. Необработанные сбои оповещают через Telegram и приводят к выходу `1`; так же ведёт себя и неудачная доставка сводки.
|
||||
|
||||
## backup {#backup}
|
||||
|
||||
### backup/run
|
||||
|
||||
`apex backup/run <telegram_bot_url>`
|
||||
|
||||
Управляет однократным запуском основного сервиса `resticontainer`, управляемого метками (профиль `manual`): проверка доступности репозитория с инициализацией при необходимости, затем один `backup`, в котором resticontainer обнаруживает каждый сервис, несущий метки compose `restic.*`, запускает их pre-хуки, останавливает все контейнеры, помеченные `restic.backup.stop`, разрешает помеченные монтирования контейнеров в пути на хосте и делает снимок объединения за один запуск restic. Снимок помечается `--host <APEX_NODE_HOST>`, `--tag biweekly`, `--compression max`. Затем защита подтверждает, что свежий снимок для этого хоста действительно создан — если метки `restic.*` не были обнаружены, резервное копирование является тихой пустой операцией, поэтому оно отказывается сообщать об успехе. Далее следует политика удержания через `forget --prune` (7 daily / 4 weekly / 12 monthly). Каждый сбой restic оповещает и приводит к выходу `1`; успех тоже оповещает, а неудачное уведомление об успехе приводит к выходу `1`.
|
||||
|
||||
## tiers {#tiers}
|
||||
|
||||
### tiers/link
|
||||
|
||||
`apex tiers/link <tier1_path> <tier2_path> <tier3_path>`
|
||||
|
||||
Строит всю структуру символьных ссылок [уровней](/apex/glossary#tier): ссылки уровня узла `compositions/@tierN`, каталоги уровней для каждого проекта, области `shared/` с обратными ссылками, ссылки `@tierN` для каждого проекта и символьную ссылку `.env` в уровень 1 (цель создаётся, если отсутствует). Существующие пути, не являющиеся символьными ссылками, запрашивают подтверждение; отказ пропускает создание этой ссылки.
|
||||
|
||||
### tiers/useradd
|
||||
|
||||
`apex tiers/useradd`
|
||||
|
||||
Гарантирует наличие группы `noroot-shared`, затем создаёт по одному заблокированному системному пользователю `noroot-<project>` на каждый проект композиции (собственная основная группа, без домашнего каталога, оболочка `nologin`, член общей группы) и внедряет `APEX_UID`/`APEX_GID`/`APEX_SHARED_GID` в `.env` каждого проекта между управляемыми маркерами.
|
||||
|
||||
### tiers/chown
|
||||
|
||||
`apex tiers/chown [tier1_path] [tier2_path] [tier3_path]`
|
||||
|
||||
Применяет матрицу владения (см. [Концепция: Многоуровневое хранилище](/apex/concepts/tiered-storage#ownership)) по корням уровней — по умолчанию используются ссылки уровней уровня узла. Требует наличия `noroot-shared` (иначе выход `1`); проекты с неполной идентичностью в `.env` пропускаются с предупреждением.
|
||||
|
||||
## utils {#utils}
|
||||
|
||||
### utils/extract-traefik-certs
|
||||
|
||||
`apex utils/extract-traefik-certs <source_acme> <dest_dir> [telegram_bot_url]`
|
||||
|
||||
Извлекает хранилище ACME в `fullchain.pem`/`privkey.pem` для каждого домена с помощью контейнеризованного дампера, затем выполняет `chmod 600` для ключей и переназначает владение выходным деревом вызывающему пользователю. Отсутствующий источник: выход `1`. Сбои внутри извлечения оповещают через Telegram, если был указан URL.
|
||||
|
||||
### utils/lint-docker-compose
|
||||
|
||||
`apex utils/lint-docker-compose [--hook]`
|
||||
|
||||
Два прохода: (1) проверка [якорных IP](/apex/glossary#anchor-ip) по `compositions/*/docker-compose.yml` — корректность формата, уникальность, нахождение внутри подсети узла; (2) линтер compose `dclint` с `--fix`, запускаемый в контейнере по репозиторию (ссылки уровней, `.env` и подмодуль commons исключены). `--hook` ограничивает проход 2 файлами compose в индексе и повторно добавляет в индекс то, что исправил линтер. Код возврата отражает оба прохода.
|
||||
|
||||
## compose {#compose}
|
||||
|
||||
### compose
|
||||
|
||||
`apex compose <action> [--dry-run] [extra...]`
|
||||
|
||||
Оркеструет `docker compose <action>` по всему узлу: сначала основной проект, затем сервисы в алфавитном порядке (обратном для `down`). `up` подразумевает `-d`; `.env`/`apex.env` передаются для каждого проекта при наличии; дополнительные аргументы пробрасываются; `--dry-run` (принимается в любой позиции) выводит команды вместо их выполнения.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Справочник: CLI](/apex/reference/cli)
|
||||
- [Руководство: Эксплуатация узла](/apex/guides/host-operations)
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Справочник CLI
|
||||
description: "Лаунчер apex: перечисление, диспетчеризация, справка и коды возврата."
|
||||
weight: 31
|
||||
---
|
||||
|
||||
Лаунчер — это единый скрипт на Python из стандартной библиотеки, `commons/apex` (в `PATH` после `init.sh`). Всё, что он делает, нацелено на локальный checkout — аргумента узла не существует.
|
||||
|
||||
## Формы вызова {#invocation}
|
||||
|
||||
| Команда | Поведение |
|
||||
|---|---|
|
||||
| `apex` или `apex --help` | Перечислить каждое доступное [действие](/apex/glossary#action) с его кратким описанием; выход `0`. |
|
||||
| `apex <group>/<action> [args...]` | Выполнить одно действие. |
|
||||
| `apex <group>/<action> --help` | Вывести справку argparse для действия, сгенерированную из его `METADATA`; выход `0`. |
|
||||
|
||||
Перечисление помечает каждую запись источником [оверлея](/apex/glossary#overlay) — без метки (commons), `(local)`, `(local override)` — и добавляет `[DISABLED: reason]`, когда модуль объявляет `DISABLED`. Перечисление использует статическое сканирование `ast`, поэтому код действий не выполняется.
|
||||
|
||||
## Раскладка и разрешение путей {#layout}
|
||||
|
||||
При запуске лаунчер определяет, в какой из двух раскладок он находится:
|
||||
|
||||
- **Checkout узла** — лаунчер находится в каталоге с именем `commons/`, родитель которого содержит `node.env`. Родитель является корнем репозитория; `proprietaries/actions/` — это дерево оверлея; [идентичность узла](/apex/glossary#node-identity) разрешается перед диспетчеризацией, и любые предупреждения об идентичности логируются первыми.
|
||||
- **Автономный commons** — любое другое расположение. Каталог commons является собственным корнем, оверлея нет, а `ctx.node` равен `None` (строки лога несут префикс `global`).
|
||||
|
||||
`sys.path` собирается так: каталог commons (делает `engine` импортируемым), затем `commons/vendor/` при наличии, затем `../proprietaries/` в checkout узла (делает проприетарный код `lib/` импортируемым).
|
||||
|
||||
## Коды возврата {#exit-codes}
|
||||
|
||||
| Код | Источник |
|
||||
|---|---|
|
||||
| `0` | Действие вернуло `0` или нецелое значение; пути `--help`. |
|
||||
| `1` | Неизвестное действие; отключённое действие; необработанное исключение (логируется как `Type: message` с префиксом контекста); нецелый `SystemExit`. |
|
||||
| `64` | Ошибка использования при разборе аргументов (`EX_USAGE`). |
|
||||
| `65` | Ошибка данных, вызванная библиотеками движка (`EX_DATAERR`) — например, пустой отрендеренный crontab. |
|
||||
| `66` | Отсутствующий ввод (`EX_NOINPUT`) — например, отсутствующие поля идентичности или отсутствующий каталог конфигурации ufw. |
|
||||
|
||||
Любой другой код — это то, что вернуло или вызвало само действие.
|
||||
|
||||
## Формат лога {#log-format}
|
||||
|
||||
Весь вывод движка и действий следует одной форме строки:
|
||||
|
||||
```text
|
||||
[<node>] [<group>/<action>] [LEVEL] message
|
||||
```
|
||||
|
||||
`LEVEL` — это `INFO`, `WARN`, `ERROR` или `SUCCESS`; строки `ERROR` идут в stderr, всё остальное — в stdout — удобно для cron как для захвата, так и для оповещения.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Действия и оверлей](/apex/concepts/actions-and-overlay)
|
||||
- [Справочник: Действия](/apex/reference/actions)
|
||||
- [Справочник: API движка](/apex/reference/engine-api)
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: Справочник API движка
|
||||
description: API дескрипторов и ctx, доступные авторам действий.
|
||||
weight: 34
|
||||
---
|
||||
|
||||
Поверхность, с которой программирует автор [действия](/apex/glossary#action): дескрипторы аргументов в `engine.descriptor` и члены контекста выполнения. Концептуальная основа — в [Концепция: Контекст выполнения](/apex/concepts/execution-context).
|
||||
|
||||
## Дескрипторы {#descriptors}
|
||||
|
||||
```python
|
||||
from engine.descriptor import Meta, Arg, Opt, Flag, Rest
|
||||
```
|
||||
|
||||
| Класс | Сигнатура | Сопоставление с argparse |
|
||||
|---|---|---|
|
||||
| `Meta` | `Meta(summary, args=[])` | Управляет как кратким описанием в перечислении, так и парсером. |
|
||||
| `Arg` | `Arg(name, help="", required=True)` | Позиционный; `required=False` → `nargs="?"`, по умолчанию `None`. |
|
||||
| `Opt` | `Opt(name, help="", default=None)` | Опция со значением (`--name VALUE`) со значением по умолчанию. |
|
||||
| `Flag` | `Flag(name, help="")` | Логический флаг `store_true`. |
|
||||
| `Rest` | `Rest(name, help="")` | Жадный остаток (`argparse.REMAINDER`). |
|
||||
|
||||
Ошибки использования приводят к выходу `64` вместо стандартного для argparse `2`.
|
||||
|
||||
> [!WARNING]
|
||||
> `Rest` захватывает *всё* после первого позиционного аргумента — включая флаги. `Flag`, объявленный рядом с `Rest`, распознаётся только *перед* позиционным аргументом; действие, которое должно принимать свой флаг в любом месте, обязано само вырезать его из остатка (действие `compose` делает именно это для `--dry-run`).
|
||||
|
||||
## ctx.log {#log}
|
||||
|
||||
`info(msg)` · `warn(msg)` · `success(msg)` → stdout; `error(msg)` → stderr. Все строки несут префикс `[<node>] [<action>] [LEVEL]`.
|
||||
|
||||
## ctx.sys {#sys}
|
||||
|
||||
| Метод | Поведение |
|
||||
|---|---|
|
||||
| `run(cmd, check=True, capture=False, cwd=None, env=None, input=None)` | Обёртка над `subprocess.run`, возвращающая `CompletedProcess`; `capture=True` собирает текстовые stdout/stderr. |
|
||||
| `sudo(cmd, **kw)` | `run(["sudo", *cmd])`. |
|
||||
| `ok(cmd, **kw)` | `True` тогда и только тогда, когда код возврата `0`; никогда не вызывает исключение. |
|
||||
| `service_exists(name)` / `service_is_active(name)` / `service_is_enabled(name)` | Запросы к юнитам systemd. |
|
||||
| `start_and_enable(name)` | Запуск + включение при необходимости; возвращает итоговое активное состояние. |
|
||||
| `ensure_running(name)` | `enable --now`, когда неактивен. |
|
||||
| `restart(name)` / `reload(name, dry_run=False)` / `daemon_reload()` | Жизненный цикл сервиса. |
|
||||
| `wait_for(name, timeout=30, interval=2)` | Опрашивать, пока не станет активным. |
|
||||
| `install_packages(packages)` | Для каждого пакета: пропустить, когда его бинарник в `PATH`, иначе `apt-get update -y` + `apt-get install -y`. |
|
||||
|
||||
## ctx.tpl {#tpl}
|
||||
|
||||
| Метод | Поведение |
|
||||
|---|---|
|
||||
| `render(path, vars)` | Отрендерить один файл (относительные пути разрешаются относительно `configs/`); семантика `safe_substitute` — неизвестные переменные остаются буквальными. |
|
||||
| `render_dir(rel_dir, vars)` | Отрендерить каждый файл в поддереве `configs/`; возвращает `{relative_path: content}`. |
|
||||
|
||||
## ctx.host {#host}
|
||||
|
||||
| Метод | Поведение |
|
||||
|---|---|
|
||||
| `backup(path, sudo=False)` | Скопировать файл в `<path>.<epoch>.bak`; возвращает путь резервной копии или `None`. |
|
||||
| `write_file(path, content, backup=True, dry_run=False, sudo=False)` | Атомарная установка через временный файл (режим `0644`); опциональная предварительная резервная копия; вариант `sudo` сохраняет режим существующей цели. |
|
||||
| `inject_block(target, content, start, end, anchor=None, old_pairs=(), dry_run=False, sudo=True)` | Замена управляемого блока по маркерам; удаляет текущие и устаревшие блоки, вставляет после первой строки-якоря; `False`, когда якорь отсутствует; отсутствующая цель — ошибка. |
|
||||
| `install_crontab(text, dry_run=False)` | Снимок, отказ при пустом рендере (`SystemExit(65)`), предпросмотр, установка через `crontab -`, восстановление снимка при сбое (`SystemExit(1)`). |
|
||||
|
||||
## ctx.ufw {#ufw}
|
||||
|
||||
`apply(rules_dir, config_dir=None, dry_run=False)` — полный проход межсетевого экрана: проверки каталогов (выход `66`), проверки инструментов (`ufw`, `ufw-docker`), правила хоста, внедрение управляемого блока в `/etc/ufw/before.rules` и `after.rules`, правила контейнеров из файлов `*.rules`.
|
||||
|
||||
## ctx.crowdsec {#crowdsec}
|
||||
|
||||
`ensure_installed()` · `deploy(configs_subdir, vars, dry_run=False)` · `install_collections(collections)`.
|
||||
|
||||
## ctx.systemd {#systemd}
|
||||
|
||||
`deploy_units(configs_subdir, vars, dry_run=False)` — отрендерить файлы верхнего уровня (не скрытые) поддерева в `/etc/systemd/system/`, затем выполнить daemon-reload и `enable --now` для каждого `.service`.
|
||||
|
||||
## ctx.notify {#notify}
|
||||
|
||||
`telegram(title, bot_url, message, node=None, level="INFO")` плюс сокращения `success` / `error` / `info` / `warn`. Все возвращают `bool` — `True` только когда сообщение было доставлено; пустой `bot_url` логирует предупреждение и возвращает `False`. Поле `instance` в оповещении по умолчанию равно публичному хосту узла (`APEX_NODE_HOST`) — префикс домена или узла не применяется.
|
||||
|
||||
## ctx.paths и ctx.node {#paths}
|
||||
|
||||
`ctx.paths`: `repo_root`, `commons`, `proprietaries`, `configs`, `compositions`, `core`, `tier1..3`, `stier1..3`, `root_tier1..3`. `ctx.node`: запись идентичности (`name`, `subnet`, `fqdn`, `host`) или `None` в checkout автономного commons. `ctx.vars()`: окружение шаблонов/compose (см. [Справочник: Переменные окружения](/apex/reference/environment)). `ctx.commons.run(args)`: делегирование затенённому действию commons — допустимо только в переопределяющем проприетарном модуле.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Контекст выполнения](/apex/concepts/execution-context)
|
||||
- [Руководство: Разработка действий](/apex/guides/action-development)
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Справочник переменных окружения
|
||||
description: Каждая переменная APEX_*, где она задаётся и кто её потребляет.
|
||||
weight: 33
|
||||
---
|
||||
|
||||
Пространство имён `APEX_*` — это вся поверхность конфигурации фреймворка. Переменные происходят из одного из четырёх слоёв; более поздние слои побеждают, когда действия оркестрации собирают окружение процесса.
|
||||
|
||||
## node.env — идентичность {#node-env}
|
||||
|
||||
Закоммичена в корне репозитория; читается при разрешении [идентичности узла](/apex/glossary#node-identity). Фреймворк агностичен к домену: узел объявляет свой собственный FQDN и публичное имя хоста — концепции кластера нет, и обязательной формы FQDN нет.
|
||||
|
||||
| Переменная | Значение | Примечания |
|
||||
|---|---|---|
|
||||
| `APEX_NODE_FQDN` | Реальный FQDN узла (маршрутизация Traefik `Host()`) | При отсутствии откатывается к имени хоста ОС (с предупреждением). |
|
||||
| `APEX_NODE_HOST` | Публичное имя хоста узла (резервное копирование `--host`, `instance` для наблюдаемости/уведомлений) | По умолчанию равно `APEX_NODE_FQDN`. Его первая метка становится коротким `name`. |
|
||||
| `APEX_SUBNET` | Сеть /24 узла для сети `direct` | Обязательна, единственный источник подсети. Отсутствие = выход `66`. |
|
||||
| `APEX_GIT_AUTHOR_NAME` | Идентичность бота для коммитов capture-up | Общее значение по умолчанию (`apex [bot]`). |
|
||||
| `APEX_GIT_AUTHOR_EMAIL` | Email идентичности бота для коммитов capture-up | Общее значение по умолчанию (`apex@localhost`). |
|
||||
|
||||
## apex.env — закоммиченные скаляры {#apex-env}
|
||||
|
||||
Закоммичены в `compositions/apex/`; несекретны по определению.
|
||||
|
||||
| Переменная | Значение | Примечания |
|
||||
|---|---|---|
|
||||
| `APEX_TRAEFIK_DIRECT_IP` | [Якорный IP](/apex/glossary#anchor-ip) краевого прокси в подсети узла | Требуется основной композицией (`?error`). |
|
||||
| `APEX_RESTIC_REPOSITORY` | Локатор репозитория резервных копий | Обязательна (`?error`). |
|
||||
| `APEX_RESTIC_COMPRESSION` | Уровень сжатия restic | Опциональна; по умолчанию `max` (политика флота — `backup/run` также принудительно задаёт `--compression max`). |
|
||||
| `APEX_RESTIC_AWS_REGION` | Регион бэкенда S3 | Опциональна; по умолчанию `us-east-1`. |
|
||||
| `APEX_RESTIC_AWS_ENDPOINT` | URL эндпоинта бэкенда S3 | Опциональна; по умолчанию пусто (AWS S3). Задавайте для любого не-AWS S3-совместимого хранилища. |
|
||||
| `APEX_LOKI_PUSH_URL` | URL приёма Loki для агента [Alloy](/apex/concepts/architecture) (`loki.write`) | Требуется профилем `alloy` (`?error`). |
|
||||
| `APEX_PROM_PUSH_URL` | URL `remote_write` Prometheus для агента Alloy | Требуется профилем `alloy` (`?error`). |
|
||||
| `APEX_OBS_BASIC_AUTH_USER` | Пользователь basic-auth для целей отправки наблюдаемости | Требуется профилем `alloy` (`?error`). |
|
||||
| `COMPOSE_PROFILES` | Подключаемые основные профили (`adguard`, `xray`, `alloy`) | Может быть пустой; `manual` активируется через `backup/run`, а не здесь. |
|
||||
| `TZ` | Часовой пояс контейнера | Опциональна; по умолчанию `Etc/UTC`. |
|
||||
|
||||
## .env — секреты {#secrets}
|
||||
|
||||
Не закоммичен; символьная ссылка в хранилище уровня 1. Ключи документируются для каждого узла в `SECRETS.md`.
|
||||
|
||||
| Переменная | Значение | Примечания |
|
||||
|---|---|---|
|
||||
| `APEX_UID`, `APEX_GID`, `APEX_SHARED_GID` | Идентичность noroot проекта | Автоматически внедряются через `tiers/useradd`; обязательны (`?error`). |
|
||||
| `APEX_RESTIC_PASSWORD` | Пароль репозитория резервных копий | Обязательна (`?error`). |
|
||||
| `APEX_RESTIC_AWS_ACCESS_KEY_ID`, `APEX_RESTIC_AWS_SECRET_ACCESS_KEY` | Учётные данные объектного хранилища | Обязательны (`?error`). |
|
||||
| `APEX_TRAEFIK_CF_DNS_API_TOKEN` | Токен DNS-challenge для ACME | Обязательна (`?error`). |
|
||||
| `APEX_TRAEFIK_ACME_EMAIL` | Email учётной записи ACME | Обязательна (`?error`). |
|
||||
| `APEX_OBS_BASIC_AUTH_PASS` | Пароль basic-auth для целей отправки наблюдаемости | Требуется профилем `alloy` (`?error`). |
|
||||
| `APEX_LOKI_BASIC_AUTH` | Пользователи htpasswd для центрального приёма Loki/Prometheus | Не требуется основной композицией. Только на узле, который сам фронтит центральный приём наблюдаемости. |
|
||||
|
||||
## Экспорты движка — ctx.vars() {#engine}
|
||||
|
||||
Вычисляются [движком](/apex/glossary#engine) при вызове; экспортируются в `docker compose` действиями оркестрации и доступны каждому рендеру шаблона. Никогда не задавайте их вручную.
|
||||
|
||||
| Переменная | Значение |
|
||||
|---|---|
|
||||
| `APEX_NODE_HOST` | Публичное имя хоста узла (`node.host`); резервное копирование `--host` и `instance` для наблюдаемости/уведомлений. |
|
||||
| `APEX_NODE_FQDN` | Реальный FQDN узла (`node.fqdn`); используется для правил краевой маршрутизации. |
|
||||
| `APEX_SUBNET` | Подсеть узла. |
|
||||
| `APEX_COMMONS` | Абсолютный каталог commons (`$APEX_COMMONS/apex` — это лаунчер, его используют crontab и юниты systemd). |
|
||||
| `APEX_REPO_ROOT` | Абсолютный корень репозитория узла. |
|
||||
| `APEX_TIER1`, `APEX_TIER2`, `APEX_TIER3` | Каталог данных основного проекта для каждого [уровня](/apex/glossary#tier). |
|
||||
| `APEX_TIER1_SHARED`, `APEX_TIER2_SHARED`, `APEX_TIER3_SHARED` | Общая область для каждого уровня. |
|
||||
| `APEX_TIER_ROOT1`, `APEX_TIER_ROOT2`, `APEX_TIER_ROOT3` | Корни хранилища уровней. |
|
||||
|
||||
> [!NOTE]
|
||||
> Переменные, помеченные выше `?error`, обеспечиваются общей основной композицией: `docker compose` отказывается вычислять модель, пока они не заданы, что превращает забытый секрет в немедленный, явный сбой.
|
||||
|
||||
Несколько переменных основной композиции используют вместо этого `${VAR:-default}`, поэтому они опциональны и переопределяемы для каждого узла: `TZ` (`Etc/UTC`), `APEX_RESTIC_AWS_REGION` (`us-east-1`), `APEX_RESTIC_AWS_ENDPOINT` (пусто), `APEX_RESTIC_COMPRESSION` (`max`) и `APEX_ENCLAVE_SUBNET` (`198.18.0.0/16`, исходный диапазон ipallowlist для маршрутов панели Traefik/adguard).
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Концепция: Контекст выполнения](/apex/concepts/execution-context)
|
||||
- [Концепция: Композиции и общее ядро](/apex/concepts/compositions)
|
||||
- [Концепция: Разрешение идентичности узла](/apex/concepts/node-identity)
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: Устранение неполадок
|
||||
description: Симптомы, диагностика и исправление типовых сбоев apex.
|
||||
weight: 40
|
||||
---
|
||||
|
||||
Режимы сбоев, о которых сообщает [движок](/apex/glossary#engine), с точными строками лога, которые он выводит.
|
||||
|
||||
## APEX_NODE_FQDN откатывается к имени хоста {#fqdn-fallback}
|
||||
|
||||
**Симптомы:** каждое действие пишет в лог `APEX_NODE_FQDN not set in node.env; using hostname '<fqdn>'.`
|
||||
|
||||
**Диагностика:** в `node.env` не задан `APEX_NODE_FQDN`, поэтому движок вывел FQDN из имени хоста ОС. На узле, чьё имя хоста и есть предполагаемый FQDN, это безобидно; в противном случае маршрутизация traefik `Host()` будет использовать неверное имя.
|
||||
|
||||
**Решение:** задайте `APEX_NODE_FQDN` явно в `node.env` в корне репозитория (например, `APEX_NODE_FQDN=node1.example.com`).
|
||||
|
||||
## Разрешение идентичности завершается с кодом 66 {#identity-66}
|
||||
|
||||
**Симптомы:** `identity: APEX_NODE_FQDN missing from node.env and no hostname` или `identity: APEX_SUBNET missing from node.env`, код выхода `66`.
|
||||
|
||||
**Диагностика:** обязательное поле идентичности не удаётся разрешить. Первая строка означает, что ни `APEX_NODE_FQDN`, ни имя хоста ОС не дают FQDN; вторая означает, что отсутствует `APEX_SUBNET`. Движок отказывается угадывать.
|
||||
|
||||
**Решение:** создайте или дополните `node.env` в корне репозитория — задайте `APEX_SUBNET` и задайте `APEX_NODE_FQDN` (либо убедитесь, что имя хоста ОС пригодно к использованию).
|
||||
|
||||
## configure/ufw завершается с кодом 66 {#ufw-66}
|
||||
|
||||
**Симптомы:** `Rules directory does not exist: ...` или `Config directory does not exist: ...`, код выхода `66`.
|
||||
|
||||
**Диагностика:** отсутствует ровно одна из директорий `configs/ufw/docker/` или `configs/ufw/host/` — это сломанная структура конфигурации, а не намеренно оставленный без правил узел.
|
||||
|
||||
**Решение:** восстановите отсутствующую директорию (пустая допустима: пустой `docker/` выводит предупреждение `No .rules files found ... nothing to apply.` и продолжает работу).
|
||||
|
||||
## Отсутствует инструментарий ufw {#ufw-tools}
|
||||
|
||||
**Симптомы:** `ufw not installed/working via sudo. Run configure/base first.` или `ufw-docker not found at ~/.local/bin/ufw-docker. Run configure/base first.`, код выхода `1`.
|
||||
|
||||
**Диагностика:** отсутствуют предварительные требования слоя фаервола.
|
||||
|
||||
**Решение:** запустите `apex configure/base`, затем повторите `apex configure/ufw`.
|
||||
|
||||
## Установка crontab отклонена или откачена {#cron}
|
||||
|
||||
**Симптомы:** `Rendered crontab is empty; aborting (previous kept).` (код выхода `65`) или `crontab install failed; restoring previous.` (код выхода `1`).
|
||||
|
||||
**Диагностика:** отрендеренный `configs/cron/crontab` оказался пустым (проблема шаблона/переменной — помните, что неизвестные `$VARS` остаются буквальными, они не обнуляют файл) или `crontab` отклонил синтаксис.
|
||||
|
||||
**Решение:** изучите выведенный предпросмотр, исправьте шаблон, повторно запустите `apex configure/cron`. Предыдущий crontab остаётся целым в обоих случаях.
|
||||
|
||||
## sync/repository прерывается {#sync-aborts}
|
||||
|
||||
**Симптомы:** `Commons submodule at <path> has uncommitted changes. Aborting.` или `Directory '<repo>/compositions' does not exist. Cannot sync.`, код выхода `1`.
|
||||
|
||||
**Диагностика:** коммит [capture-up](/apex/glossary#capture-up) отказывается фиксировать сломанный чекаут — грязный закреплённый субмодуль или отсутствующее дерево [композиций](/apex/glossary#compositions).
|
||||
|
||||
**Решение:** для грязного субмодуля — `git -C commons checkout -- .` (или закоммитьте намеренное перемещение пина); для отсутствующего дерева — восстановите чекаут до следующего планового запуска.
|
||||
|
||||
## Telegram не доставлен {#telegram}
|
||||
|
||||
**Симптомы:** `Telegram Bot URL not provided. Skipping notification.` или `Failed to send Telegram notification: <error>` — и действия, зависящие от уведомлений (`backup/run`, `sync/repository`, `sync/packages`), завершаются с кодом `1`, даже если их работа выполнена успешно.
|
||||
|
||||
**Диагностика:** аргумент с URL бота был пустым или отправка не удалась (сеть, прокси, отозванный токен). Ненулевой код выхода сделан намеренно: автоматический запуск, статус которого нельзя сообщить, считается неуспешным.
|
||||
|
||||
**Решение:** проверьте файл-источник URL, который читает строка cron, доступность сети/прокси и токен бота; повторите запуск вручную, чтобы подтвердить доставку.
|
||||
|
||||
## docker compose отклоняет базовую обёртку {#compose-include}
|
||||
|
||||
**Симптомы:** `docker compose` выдаёт ошибку на ключевом слове `include` при вычислении `compositions/apex/docker-compose.yml`.
|
||||
|
||||
**Диагностика:** [базовая композиция](/apex/glossary#core-composition) подключается через `include:`, что требует Docker Compose v2.20+.
|
||||
|
||||
**Решение:** обновите плагин Compose (`docker compose version` для проверки).
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Справочник: CLI](/apex/reference/cli)
|
||||
- [Руководство: Эксплуатация узла](/apex/guides/host-operations)
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -1,12 +1,17 @@
|
||||
---
|
||||
title: Documentation of ermnvldmr.com root deployment commons
|
||||
description: Документация общих модулей корневой инфраструктуры ermnvldmr.com.
|
||||
weight: 1
|
||||
weight: 2
|
||||
type: docs
|
||||
cascade:
|
||||
type: docs
|
||||
---
|
||||
|
||||
> [!WARNING]
|
||||
> **Deprecated.** The root framework has been superseded by the
|
||||
> [apex fleet operations framework](/en/apex) (documentation in English only). This
|
||||
> section is retained for reference only and no longer receives updates.
|
||||
|
||||
Фреймворк `ermnvldmr.com-root-commons` предоставляет среду развертывания с минимальными зависимостями для независимых self-hosted проектов.
|
||||
|
||||
Общая логика, скрипты и шаблоны развертывания, используемые в различных приложениях, хранятся в репозитории [GitHub: ermnvldmr.com-root-commons](https://github.com/deytenit/ermnvldmr.com-root-commons).
|
||||
|
||||
Reference in new issue
Block a user