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: 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:
name: Build (${{ matrix.os }})
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).
LOCALE_DIR := src/nexus_sync/locale
@@ -38,5 +38,12 @@ i18n-compile:
test:
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:
rm -rf build dist *.spec
rm -rf build dist *.spec docs/_build
+89 -89
View File
@@ -2,15 +2,15 @@
## Transport
- Протокол: HTTPS.
- Формат тела запроса и ответа: JSON.
- Префикс API: `/api/v1`.
- Формат времени: RFC 3339 / ISO 8601, например `2026-05-24T13:20:30Z`.
- Авторизация клиента: bearer token в заголовке `Authorization`.
- Protocol: HTTPS.
- Request and response body format: JSON.
- API prefix: `/api/v1`.
- Time format: RFC 3339 / ISO 8601, e.g. `2026-05-24T13:20:30Z`.
- Client authorization: bearer token in the `Authorization` header.
Пример заголовков:
Example headers:
```http
```text
Content-Type: application/json
Authorization: Bearer <client-token>
```
@@ -19,12 +19,12 @@ Authorization: Bearer <client-token>
### `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
@@ -45,19 +45,19 @@ Authorization: Bearer <client-token>
}
```
Поля:
Fields:
- `client_id` - стабильный идентификатор, заданный при настройке клиента. Он не
должен меняться при каждом рестарте.
- `observed_at` - момент, когда клиент подготовил payload.
- `client.hostname` - текущий hostname машины.
- `client.platform` - платформа клиента. Желательно использовать названия,
близкие к Python/platform: `linux`, `darwin`, `windows`.
- `client.version` - версия nexus-sync client.
- `state` - намеренно маленький объект. Может быть сюда позже можно добавить IP,
disk usage, memory, battery status и другие метрики.
- `last_command_result` - `null`, если клиенту нечего нового сообщать о
выполнении команды.
- `client_id` - a stable identifier set during client setup. It must not change
on every restart.
- `observed_at` - the moment the client prepared the payload.
- `client.hostname` - the machine's current hostname.
- `client.platform` - the client platform. Prefer names close to
Python/platform: `linux`, `darwin`, `windows`.
- `client.version` - the nexus-sync client version.
- `state` - an intentionally small object. IP, disk usage, memory, battery
status and other metrics may be added here later.
- `last_command_result` - `null` if the client has nothing new to report about
command execution.
### Request with command result
@@ -86,17 +86,16 @@ Authorization: Bearer <client-token>
}
```
Поля результата:
Result fields:
- `command_id` должен совпадать с id команды, которую сервер ранее вернул
клиенту.
- `status` принимает одно из значений: `succeeded`, `failed`, `timed_out`,
`rejected`.
- `return_code` - exit code процесса, если процесс был запущен. Для
`timed_out` или `rejected` поле может быть `null`, если финального exit code
нет.
- `stdout` и `stderr` клиент должен ограничивать по размеру перед отправкой.
Конкретный лимит байт в этом контракте пока не фиксируется.
- `command_id` must match the id of the command the server previously returned
to the client.
- `status` takes one of: `succeeded`, `failed`, `timed_out`, `rejected`.
- `return_code` - the process exit code, if the process was started. For
`timed_out` or `rejected` the field may be `null` when there is no final exit
code.
- `stdout` and `stderr` must be size-limited by the client before sending. This
contract does not yet fix a specific byte limit.
### Response without command
@@ -126,36 +125,37 @@ Authorization: Bearer <client-token>
}
```
Поля команды:
Command fields:
- `id` генерируется сервером и должен быть уникальным.
- `kind` описывает тип executor. Пока определён только `exec`.
- `name` - имя command preset.
- `args` содержит аргументы, специфичные для конкретного preset.
- `timeout_seconds` - максимальное время выполнения, которое сервер допускает
для этой команды.
- `id` is generated by the server and must be unique.
- `kind` describes the executor type. Only `exec` is defined so far.
- `name` - the command preset name.
- `args` holds arguments specific to a particular preset.
- `timeout_seconds` - the maximum execution time the server allows for this
command.
Клиент обязан выполнять только те команды, которые он знает и локально
разрешает. Неизвестные или запрещённые команды нужно возвращать как `rejected`.
The client must run only commands it knows and locally allows. Unknown or
forbidden commands must be returned as `rejected`.
## Server-side API
Эти ручки нужны серверной части/админке, чтобы видеть клиентов и ставить им
команды в очередь. Клиенты напрямую используют только heartbeat.
These endpoints are for the server/admin side, to see clients and queue commands
for them. Clients directly use only the heartbeat.
### `GET /api/v1/server/clients`
Возвращает список известных клиентов с последними heartbeat-данными и
Returns the list of known clients with their latest heartbeat data and
`available_commands`.
### `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`
Создаёт 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:
@@ -188,66 +188,66 @@ Response:
### `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:
1. `pending`: команда создана на сервере и ещё не доставлена клиенту.
2. `delivered`: команда была возвращена клиенту в heartbeat response.
3. `succeeded`: клиент сообщил об успешном выполнении.
4. `failed`: клиент сообщил об ошибке выполнения.
5. `timed_out`: клиент сообщил о timeout.
6. `rejected`: клиент отказался выполнять команду.
1. `pending`: the command was created on the server and not yet delivered to the
client.
2. `delivered`: the command was returned to the client in a heartbeat response.
3. `succeeded`: the client reported successful execution.
4. `failed`: the client reported an execution error.
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
Heartbeat endpoint должен использовать такие HTTP-статусы:
The heartbeat endpoint should use these HTTP statuses:
- `200 OK`: heartbeat принят; тело ответа соответствует контракту выше.
- `400 Bad Request`: некорректный JSON или невалидные значения полей.
- `401 Unauthorized`: bearer token отсутствует или невалиден.
- `403 Forbidden`: token валиден, но не имеет права действовать как этот
- `200 OK`: heartbeat accepted; the response body matches the contract above.
- `400 Bad Request`: malformed JSON or invalid field values.
- `401 Unauthorized`: the bearer token is missing or invalid.
- `403 Forbidden`: the token is valid but not allowed to act as this
`client_id`.
- `409 Conflict`: результат ссылается на неизвестную, уже терминальную или
несовместимую команду.
- `429 Too Many Requests`: клиент опрашивает сервер слишком часто.
- `500 Internal Server Error`: неожиданная ошибка сервера.
- `409 Conflict`: the result references an unknown, already terminal, or
incompatible command.
- `429 Too Many Requests`: the client polls the server too often.
- `500 Internal Server Error`: an unexpected server error.
Состояние выполнения команды выражается полями в JSON, а не HTTP-ошибками.
Например, команда с exit code `1` всё равно отправляется через успешный
Command execution state is expressed via JSON fields, not HTTP errors. For
example, a command with exit code `1` is still delivered over a successful
heartbeat request.
## Polling rules
Ответ сервера содержит `next_poll_after_seconds`.
The server response contains `next_poll_after_seconds`.
Поведение:
Behavior:
- обычный polling interval без команды: 60 секунд;
- polling interval после получения команды: 10 секунд;
- клиентский минимальный interval: 5 секунд;
- клиентский максимальный interval: 300 секунд.
- normal polling interval without a command: 60 seconds;
- polling interval after receiving a command: 10 seconds;
- client minimum interval: 5 seconds;
- client maximum interval: 300 seconds.
Клиент должен воспринимать значение сервера как рекомендацию и зажимать его в
локальные min/max границы.
The client should treat the server value as a recommendation and clamp it to its
local min/max bounds.
## Security constraints
Необходимо соблюдать эти ограничения:
The following constraints must be respected:
- не выполнять произвольные shell-строки, полученные от сервера;
- выполнять только локально известные command presets;
- требовать bearer token для клиентских endpoint'ов;
- включать `command_id` в каждый результат, чтобы не было неоднозначного
сопоставления;
- хранить command output как логи, а не как доверенные управляющие данные;
- ограничивать размер command output перед отправкой на сервер;
- выполнять каждую команду с timeout.
- do not run arbitrary shell strings received from the server;
- run only locally known command presets;
- require a bearer token for client endpoints;
- include `command_id` in every result to avoid ambiguous matching;
- store command output as logs, not as trusted control data;
- limit the size of command output before sending it to the server;
- run every command with a 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.
+18 -15
View File
@@ -1,14 +1,17 @@
# 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*
```
Возможно нужна доп инфа о работе команды (код ошибки как минимум)
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-файле.
Серверу отправляются только `name` и `description`; поле `cmd` остаётся только
на клиенте и не управляется сервером.
The client runs only the commands described in its local YAML config file. Only
`name` and `description` are sent to the server; the `cmd` field stays on the
client and is not controlled by the server.
Пример:
Example:
```yaml
server_url: "http://127.0.0.1:5852"
@@ -52,6 +55,6 @@ allowed_commands:
logging_level: "INFO"
```
Файл ищется как `nexus.yml`/`nexus.yaml` в текущей директории, затем в
`$XDG_CONFIG_HOME`, затем в `~/.config`, затем как
The file is looked up as `nexus.yml`/`nexus.yaml` in the current directory, then
in `$XDG_CONFIG_HOME`, then in `~/.config`, then as
`~/.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
TODO:
- описать формально все API
- добавить сами команды, возможно сделать их в виде пресетов для платформы
- formally describe the whole API
- add the commands themselves, possibly as per-platform presets
## Основная суть
## Core idea
- API ручки. Доступ чисто по ssl (но это уже зона ответственности nginx)
- клиенты по своему некоторому ключу будут авторизовываться.
- проблема ручной настройки (но она тут будто минимальная, не так плохо)
- когда клиент что-либо присылает, в ответ ему надо кинуть команды на исполнение, если есть
- со стороны клиента должен быть настроен trust к серверу
- 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)
- 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. получить список всех клиентов
- с отдельным параметром "только активированные"
2. получить данные по клиенту
- сюда включается вся инфа о его показателях, когда был в сети + какие команды на него доступны (да, будем их ограничивать, возможно по модели zero-trust)
3. выполнить какую-то команду
- тут вопрос в том, будем ли мы ждать ответа от нашего клиента (вряд ли)
4. получить токен, авторизация
5. добавить кастомную команду (для клиента)
- проблема в системах, если хочется универсального добавления. Выход - добавлять только для одного клиента (лучше во всём будет)
1. get the list of all clients
- with a separate "only activated" parameter
2. get data for a client
- this includes all info about its metrics, when it was last online, and which
commands are available for it (yes, we'll restrict them, possibly via a
zero-trust model)
3. run some command
- 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. Прислать информацию
- просто стучится со своим uuid и кидает, что знает
2. Инфо о сервере?
- возможно фетчить айпи (чтобы стучаться, если сертификат на месте), другие домены
- какие-то особые "правила", если пакет с их установкой потеряется (сам то пакет вряд ли потеряется, мы об этом узнаем сразу)
1. Send information
- just knocks with its uuid and sends what it knows
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",
"Babel>=2.14.0"
]
docs = [
"sphinx>=7.0",
"myst-parser>=2.0",
"furo>=2024.1.29"
]
[project.scripts]
nexus-cli = "nexus_sync.cli.__main__:main"