add Sphinx documentation

This commit is contained in:
ars
2026-06-25 02:39:25 +03:00
parent 66b60b9bcb
commit 6261403503
10 changed files with 283 additions and 142 deletions
+11
View File
@@ -40,6 +40,17 @@ jobs:
- run: pip install -e ".[dev]" - run: pip install -e ".[dev]"
- run: pytest - run: pytest
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -e ".[docs]"
- run: sphinx-build -b html -W --keep-going docs docs/_build/html
build: build:
name: Build (${{ matrix.os }}) name: Build (${{ matrix.os }})
needs: [format, typecheck, test] needs: [format, typecheck, test]
+9 -2
View File
@@ -1,4 +1,4 @@
.PHONY: all client server cli test clean i18n-extract i18n-init i18n-update i18n-compile .PHONY: all client server cli test clean docs docs-clean i18n-extract i18n-init i18n-update i18n-compile
# CLI localization (nexus-cli strings only; daemon/server logs stay in English). # CLI localization (nexus-cli strings only; daemon/server logs stay in English).
LOCALE_DIR := src/nexus_sync/locale LOCALE_DIR := src/nexus_sync/locale
@@ -38,5 +38,12 @@ i18n-compile:
test: test:
pytest pytest
# Build the Sphinx HTML documentation into docs/_build/html.
docs:
sphinx-build -b html docs docs/_build/html
docs-clean:
rm -rf docs/_build
clean: clean:
rm -rf build dist *.spec rm -rf build dist *.spec docs/_build
+89 -89
View File
@@ -2,15 +2,15 @@
## Transport ## Transport
- Протокол: HTTPS. - Protocol: HTTPS.
- Формат тела запроса и ответа: JSON. - Request and response body format: JSON.
- Префикс API: `/api/v1`. - API prefix: `/api/v1`.
- Формат времени: RFC 3339 / ISO 8601, например `2026-05-24T13:20:30Z`. - Time format: RFC 3339 / ISO 8601, e.g. `2026-05-24T13:20:30Z`.
- Авторизация клиента: bearer token в заголовке `Authorization`. - Client authorization: bearer token in the `Authorization` header.
Пример заголовков: Example headers:
```http ```text
Content-Type: application/json Content-Type: application/json
Authorization: Bearer <client-token> Authorization: Bearer <client-token>
``` ```
@@ -19,12 +19,12 @@ Authorization: Bearer <client-token>
### `POST /api/v1/client/heartbeat` ### `POST /api/v1/client/heartbeat`
Клиент вызывает эту ручку на каждом polling tick. Одна и та же ручка The client calls this endpoint on every polling tick. The same endpoint serves
используется для трёх сценариев: three scenarios:
- сообщение "я жив"; - an "I'm alive" message;
- регулярное обновление состояния; - a regular state update;
- отправка результата последней завершённой команды. - reporting the result of the last finished command.
### Request ### Request
@@ -45,19 +45,19 @@ Authorization: Bearer <client-token>
} }
``` ```
Поля: Fields:
- `client_id` - стабильный идентификатор, заданный при настройке клиента. Он не - `client_id` - a stable identifier set during client setup. It must not change
должен меняться при каждом рестарте. on every restart.
- `observed_at` - момент, когда клиент подготовил payload. - `observed_at` - the moment the client prepared the payload.
- `client.hostname` - текущий hostname машины. - `client.hostname` - the machine's current hostname.
- `client.platform` - платформа клиента. Желательно использовать названия, - `client.platform` - the client platform. Prefer names close to
близкие к Python/platform: `linux`, `darwin`, `windows`. Python/platform: `linux`, `darwin`, `windows`.
- `client.version` - версия nexus-sync client. - `client.version` - the nexus-sync client version.
- `state` - намеренно маленький объект. Может быть сюда позже можно добавить IP, - `state` - an intentionally small object. IP, disk usage, memory, battery
disk usage, memory, battery status и другие метрики. status and other metrics may be added here later.
- `last_command_result` - `null`, если клиенту нечего нового сообщать о - `last_command_result` - `null` if the client has nothing new to report about
выполнении команды. command execution.
### Request with command result ### Request with command result
@@ -86,17 +86,16 @@ Authorization: Bearer <client-token>
} }
``` ```
Поля результата: Result fields:
- `command_id` должен совпадать с id команды, которую сервер ранее вернул - `command_id` must match the id of the command the server previously returned
клиенту. to the client.
- `status` принимает одно из значений: `succeeded`, `failed`, `timed_out`, - `status` takes one of: `succeeded`, `failed`, `timed_out`, `rejected`.
`rejected`. - `return_code` - the process exit code, if the process was started. For
- `return_code` - exit code процесса, если процесс был запущен. Для `timed_out` or `rejected` the field may be `null` when there is no final exit
`timed_out` или `rejected` поле может быть `null`, если финального exit code code.
нет. - `stdout` and `stderr` must be size-limited by the client before sending. This
- `stdout` и `stderr` клиент должен ограничивать по размеру перед отправкой. contract does not yet fix a specific byte limit.
Конкретный лимит байт в этом контракте пока не фиксируется.
### Response without command ### Response without command
@@ -126,36 +125,37 @@ Authorization: Bearer <client-token>
} }
``` ```
Поля команды: Command fields:
- `id` генерируется сервером и должен быть уникальным. - `id` is generated by the server and must be unique.
- `kind` описывает тип executor. Пока определён только `exec`. - `kind` describes the executor type. Only `exec` is defined so far.
- `name` - имя command preset. - `name` - the command preset name.
- `args` содержит аргументы, специфичные для конкретного preset. - `args` holds arguments specific to a particular preset.
- `timeout_seconds` - максимальное время выполнения, которое сервер допускает - `timeout_seconds` - the maximum execution time the server allows for this
для этой команды. command.
Клиент обязан выполнять только те команды, которые он знает и локально The client must run only commands it knows and locally allows. Unknown or
разрешает. Неизвестные или запрещённые команды нужно возвращать как `rejected`. forbidden commands must be returned as `rejected`.
## Server-side API ## Server-side API
Эти ручки нужны серверной части/админке, чтобы видеть клиентов и ставить им These endpoints are for the server/admin side, to see clients and queue commands
команды в очередь. Клиенты напрямую используют только heartbeat. for them. Clients directly use only the heartbeat.
### `GET /api/v1/server/clients` ### `GET /api/v1/server/clients`
Возвращает список известных клиентов с последними heartbeat-данными и Returns the list of known clients with their latest heartbeat data and
`available_commands`. `available_commands`.
### `GET /api/v1/server/clients/{client_id}` ### `GET /api/v1/server/clients/{client_id}`
Возвращает одного клиента или `404`, если сервер ещё не видел этот `client_id`. Returns a single client, or `404` if the server has not seen this `client_id`
yet.
### `POST /api/v1/server/clients/{client_id}/commands` ### `POST /api/v1/server/clients/{client_id}/commands`
Создаёт pending-команду для клиента. Клиент получит её на следующем heartbeat. Creates a pending command for the client. The client receives it on its next
Эта ручка принимает имя команды напрямую. heartbeat. This endpoint accepts the command name directly.
Request: Request:
@@ -188,66 +188,66 @@ Response:
### `GET /api/v1/server/commands/{command_id}` ### `GET /api/v1/server/commands/{command_id}`
Возвращает команду и, если клиент уже отчитался, поле `result`. Returns the command and, if the client has already reported, the `result` field.
## Command lifecycle ## Command lifecycle
Жизненный цикл команды: Command lifecycle:
1. `pending`: команда создана на сервере и ещё не доставлена клиенту. 1. `pending`: the command was created on the server and not yet delivered to the
2. `delivered`: команда была возвращена клиенту в heartbeat response. client.
3. `succeeded`: клиент сообщил об успешном выполнении. 2. `delivered`: the command was returned to the client in a heartbeat response.
4. `failed`: клиент сообщил об ошибке выполнения. 3. `succeeded`: the client reported successful execution.
5. `timed_out`: клиент сообщил о timeout. 4. `failed`: the client reported an execution error.
6. `rejected`: клиент отказался выполнять команду. 5. `timed_out`: the client reported a timeout.
6. `rejected`: the client refused to run the command.
Терминальные статусы: `succeeded`, `failed`, `timed_out`, `rejected`. Terminal statuses: `succeeded`, `failed`, `timed_out`, `rejected`.
## HTTP statuses ## HTTP statuses
Heartbeat endpoint должен использовать такие HTTP-статусы: The heartbeat endpoint should use these HTTP statuses:
- `200 OK`: heartbeat принят; тело ответа соответствует контракту выше. - `200 OK`: heartbeat accepted; the response body matches the contract above.
- `400 Bad Request`: некорректный JSON или невалидные значения полей. - `400 Bad Request`: malformed JSON or invalid field values.
- `401 Unauthorized`: bearer token отсутствует или невалиден. - `401 Unauthorized`: the bearer token is missing or invalid.
- `403 Forbidden`: token валиден, но не имеет права действовать как этот - `403 Forbidden`: the token is valid but not allowed to act as this
`client_id`. `client_id`.
- `409 Conflict`: результат ссылается на неизвестную, уже терминальную или - `409 Conflict`: the result references an unknown, already terminal, or
несовместимую команду. incompatible command.
- `429 Too Many Requests`: клиент опрашивает сервер слишком часто. - `429 Too Many Requests`: the client polls the server too often.
- `500 Internal Server Error`: неожиданная ошибка сервера. - `500 Internal Server Error`: an unexpected server error.
Состояние выполнения команды выражается полями в JSON, а не HTTP-ошибками. Command execution state is expressed via JSON fields, not HTTP errors. For
Например, команда с exit code `1` всё равно отправляется через успешный example, a command with exit code `1` is still delivered over a successful
heartbeat request. heartbeat request.
## Polling rules ## Polling rules
Ответ сервера содержит `next_poll_after_seconds`. The server response contains `next_poll_after_seconds`.
Поведение: Behavior:
- обычный polling interval без команды: 60 секунд; - normal polling interval without a command: 60 seconds;
- polling interval после получения команды: 10 секунд; - polling interval after receiving a command: 10 seconds;
- клиентский минимальный interval: 5 секунд; - client minimum interval: 5 seconds;
- клиентский максимальный interval: 300 секунд. - client maximum interval: 300 seconds.
Клиент должен воспринимать значение сервера как рекомендацию и зажимать его в The client should treat the server value as a recommendation and clamp it to its
локальные min/max границы. local min/max bounds.
## Security constraints ## Security constraints
Необходимо соблюдать эти ограничения: The following constraints must be respected:
- не выполнять произвольные shell-строки, полученные от сервера; - do not run arbitrary shell strings received from the server;
- выполнять только локально известные command presets; - run only locally known command presets;
- требовать bearer token для клиентских endpoint'ов; - require a bearer token for client endpoints;
- включать `command_id` в каждый результат, чтобы не было неоднозначного - include `command_id` in every result to avoid ambiguous matching;
сопоставления; - store command output as logs, not as trusted control data;
- хранить command output как логи, а не как доверенные управляющие данные; - limit the size of command output before sending it to the server;
- ограничивать размер command output перед отправкой на сервер; - run every command with a timeout.
- выполнять каждую команду с timeout.
Эти ограничения намеренно являются частью контракта, потому что проект These constraints are intentionally part of the contract because the project
занимается удалённым управлением машинами. Дешевле строить первую реализацию deals with remote machine management. It is cheaper to build the first
вокруг них, чем добавлять их задним числом. implementation around them than to add them after the fact.
+1 -1
View File
@@ -1,4 +1,4 @@
# nexus-cli # CLI
`nexus-cli` is a small non-interactive CLI for the nexus-sync server API. `nexus-cli` is a small non-interactive CLI for the nexus-sync server API.
+18 -15
View File
@@ -1,14 +1,17 @@
# Client # Client
Основная суть - кидает на известный по ip/домену сервак свой ключ и базовую информацию о себе: hostname, местное время (что-то ещё?) The core idea - it sends its key and basic info about itself (hostname, local
time, anything else?) to a server known by ip/domain.
В ответ может получить как простое "ок", так и команду для выполнения In response it can get either a plain "ok" or a command to execute.
Запросы на сервер будет кидать с некоторой частотой, постепенно возрастающей, но резко снижающейся при получении команды в ответе (вдруг надо ещё что-то выполнить) It sends requests to the server at a certain frequency, gradually increasing but
dropping sharply when a command is received in the response (in case something
else needs to be executed).
Есть установление нижнего предела на частоту (условная 1/минута) There is a lower bound on the frequency (roughly 1/minute).
Пример работы Example flow
``` ```
... ...
@@ -25,18 +28,18 @@ client -> server: hello!, i'm $(hostname), uuid= , ts=, stdout= , stderr= , ...
*waits 23s* *waits 23s*
``` ```
Возможно нужна доп инфа о работе команды (код ошибки как минимум) Additional info about command execution may be needed (at least the error code).
+ возможно на клиенте стоит ограничить набор допустимых команд + the set of allowed commands should probably be limited on the client
+ стоит явно задуматься о шифровании/идентификации сервера + encryption / server identification should be considered explicitly
## Разрешённые команды ## Allowed commands
Клиент исполняет только команды, описанные в локальном YAML config-файле. The client runs only the commands described in its local YAML config file. Only
Серверу отправляются только `name` и `description`; поле `cmd` остаётся только `name` and `description` are sent to the server; the `cmd` field stays on the
на клиенте и не управляется сервером. client and is not controlled by the server.
Пример: Example:
```yaml ```yaml
server_url: "http://127.0.0.1:5852" server_url: "http://127.0.0.1:5852"
@@ -52,6 +55,6 @@ allowed_commands:
logging_level: "INFO" logging_level: "INFO"
``` ```
Файл ищется как `nexus.yml`/`nexus.yaml` в текущей директории, затем в The file is looked up as `nexus.yml`/`nexus.yaml` in the current directory, then
`$XDG_CONFIG_HOME`, затем в `~/.config`, затем как in `$XDG_CONFIG_HOME`, then in `~/.config`, then as
`~/.config/nexus/config.yml`/`.yaml`. `~/.config/nexus/config.yml`/`.yaml`.
+39
View File
@@ -0,0 +1,39 @@
"""Sphinx configuration for the nexus-sync documentation."""
import os
import sys
# Make the package importable for autodoc (sources live under src/).
sys.path.insert(0, os.path.abspath("../src"))
project = "nexus-sync"
author = "nexus-sync"
copyright = "2026, nexus-sync"
release = "0.1.0"
extensions = [
"myst_parser",
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.napoleon",
"sphinx.ext.viewcode",
]
# Pull docstrings even from undocumented members so the API pages are useful
# while docstring coverage grows.
autosummary_generate = True
autodoc_default_options = {
"members": True,
"undoc-members": True,
"show-inheritance": True,
}
autodoc_typehints = "description"
# MyST so the existing Markdown guides render as-is.
myst_enable_extensions = ["colon_fence", "deflist"]
source_suffix = {".md": "markdown", ".rst": "restructuredtext"}
exclude_patterns = ["_build", ".DS_Store", "Thumbs.db", "archive"]
html_theme = "furo"
html_title = "nexus-sync"
+24
View File
@@ -0,0 +1,24 @@
# nexus-sync
A utility that allows you to manage your computers from a centralized server.
nexus-sync has three parts: a central **server** (FastAPI), a **client** agent
that runs on each managed machine and executes a whitelist of commands, and
**nexus-cli**, the operator's command-line tool.
```{toctree}
:maxdepth: 2
:caption: Guide
api
cli
client
server
```
```{toctree}
:maxdepth: 2
:caption: Reference
reference
```
+43
View File
@@ -0,0 +1,43 @@
# API Reference
Auto-generated from the source docstrings.
## CLI (`nexus-cli`)
```{eval-rst}
.. automodule:: nexus_sync.cli.__main__
```
## Client
```{eval-rst}
.. automodule:: nexus_sync.client.runtime
.. automodule:: nexus_sync.client.execute
```
## Server
```{eval-rst}
.. automodule:: nexus_sync.server.app
.. automodule:: nexus_sync.server.store
.. automodule:: nexus_sync.server.sqlalchemy_store
.. automodule:: nexus_sync.server.config
```
## Common
```{eval-rst}
.. automodule:: nexus_sync.common.models
```
## Utilities
```{eval-rst}
.. automodule:: nexus_sync.i18n
.. automodule:: nexus_sync.utils.log_config
```
+44 -35
View File
@@ -1,50 +1,59 @@
# Server # Server
TODO: TODO:
- описать формально все API - formally describe the whole API
- добавить сами команды, возможно сделать их в виде пресетов для платформы - add the commands themselves, possibly as per-platform presets
## Основная суть ## Core idea
- API ручки. Доступ чисто по ssl (но это уже зона ответственности nginx) - API endpoints. Access purely over SSL (but that's already nginx's
- клиенты по своему некоторому ключу будут авторизовываться. responsibility).
- проблема ручной настройки (но она тут будто минимальная, не так плохо) - clients authorize with some key of their own.
- когда клиент что-либо присылает, в ответ ему надо кинуть команды на исполнение, если есть - the manual setup problem (but it seems minimal here, not too bad)
- со стороны клиента должен быть настроен trust к серверу - when a client sends anything, the response should include commands to execute,
if any.
- the client side must be configured to trust the server.
Для сервера необходимо несколько настроек The server needs several settings
- лимит на кол-во запросов (от одного клиента, условно) - a request rate limit (per single client, roughly)
- лимит на хранение инфо (и хранить ли старое? да, логи) - a limit on stored info (and whether to keep old data? yes, logs)
- лимит на одного юзера - per-user limit
- общий лимит - global limit
- кастомные команды - custom commands
- отслеживание получения/выполнение команды сервером, повторные попытки, лимит попыток - tracking command delivery/execution by the server, retries, attempt limit
## Возможности (API ручки) ## Capabilities (API endpoints)
'Понятия не имею, как описывать эти ручки (точнее, нет желания правильно их описывать)' 'No idea how to describe these endpoints (or rather, no desire to describe them
properly)'
### Ручки для админа ### Admin endpoints
Все пользователи будут считаться админами для удобства. Остальных нет All users are treated as admins for convenience. There are no others.
1. получить список всех клиентов 1. get the list of all clients
- с отдельным параметром "только активированные" - with a separate "only activated" parameter
2. получить данные по клиенту 2. get data for a client
- сюда включается вся инфа о его показателях, когда был в сети + какие команды на него доступны (да, будем их ограничивать, возможно по модели zero-trust) - this includes all info about its metrics, when it was last online, and which
3. выполнить какую-то команду commands are available for it (yes, we'll restrict them, possibly via a
- тут вопрос в том, будем ли мы ждать ответа от нашего клиента (вряд ли) zero-trust model)
4. получить токен, авторизация 3. run some command
5. добавить кастомную команду (для клиента) - the question here is whether we'll wait for a response from our client
- проблема в системах, если хочется универсального добавления. Выход - добавлять только для одного клиента (лучше во всём будет) (probably not)
4. obtain a token, authorization
5. add a custom command (for a client)
- the problem is with systems if you want universal addition. The way out is
to add it only for a single client (better all around)
### Ручки для клиентов ### Client endpoints
Не уверен даже, что больше одной нужно будет Not even sure more than one will be needed
1. Прислать информацию 1. Send information
- просто стучится со своим uuid и кидает, что знает - just knocks with its uuid and sends what it knows
2. Инфо о сервере? 2. Info about the server?
- возможно фетчить айпи (чтобы стучаться, если сертификат на месте), другие домены - possibly fetch the ip (to knock on, if the certificate is in place), other
- какие-то особые "правила", если пакет с их установкой потеряется (сам то пакет вряд ли потеряется, мы об этом узнаем сразу) domains
- some special "rules" if the package with their installation gets lost (the
package itself is unlikely to be lost, we'll know immediately)
+5
View File
@@ -28,6 +28,11 @@ dev = [
"pre-commit>=4.0", "pre-commit>=4.0",
"Babel>=2.14.0" "Babel>=2.14.0"
] ]
docs = [
"sphinx>=7.0",
"myst-parser>=2.0",
"furo>=2024.1.29"
]
[project.scripts] [project.scripts]
nexus-cli = "nexus_sync.cli.__main__:main" nexus-cli = "nexus_sync.cli.__main__:main"