update_server/docs/API.md

631 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Update Server API
Этот документ описывает текущий публичный HTTP API сервиса. Его можно передать
клиентскому приложению или агенту, который встраивает автообновление.
Примеры ниже используют:
```text
Base URL: https://updates.example.com
API key: upsk_replace_me
Project: desktop-app
```
В реальной интеграции замени `Base URL`, API key и `project_slug` на свои
значения.
## Общая модель
Update Server хранит проекты и релизы:
- `project` - приложение, продукт или канал обновлений;
- `release` - загруженный файл обновления внутри проекта;
- `api key` - клиентский ключ доступа к API.
Администратор работает через web UI:
```text
/admin
/admin/projects
/admin/api-keys
```
Клиентские приложения работают через JSON API:
```text
/api/v1
```
На текущий момент JSON API предназначен для чтения и скачивания обновлений.
Создание проектов, загрузка релизов и создание API keys выполняются через admin
web UI, а не через публичный JSON API.
## Авторизация
Защищенные endpoint'ы требуют bearer token:
```http
Authorization: Bearer <api_key>
```
Пример:
```bash
curl -i \
-H "Authorization: Bearer upsk_replace_me" \
https://updates.example.com/api/v1/projects
```
API key должен быть:
- активным;
- не истекшим по `expires_at`, если срок задан;
- с разрешением `can_download`;
- со scope, который разрешает доступ к нужному проекту.
Поддерживаемые scope modes:
```text
all_projects
project_allow_list
project_deny_list
tag_allow_list
tag_deny_list
```
Полный API key показывается в admin UI только один раз при создании. Сервер
хранит только hash ключа.
## Формат JSON
Все JSON-ответы возвращаются с:
```http
Content-Type: application/json; charset=utf-8
```
Поля времени возвращаются строкой в формате RFC3339/RFC3339Nano, например:
```json
"created_at": "2026-06-11T10:15:30Z"
```
Относительные URL в ответах, например `download_url`, нужно склеивать с
`Base URL` клиента.
## Health Check
### GET /healthz
Публичная проверка готовности HTTP-сервера и SQLite.
```bash
curl -i https://updates.example.com/healthz
```
Успешный ответ:
```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
```
```json
{
"database": "ok",
"service": "Update Server",
"status": "ok",
"timestamp": "2026-06-11T10:15:30Z"
}
```
Если база недоступна:
```http
HTTP/1.1 503 Service Unavailable
```
```json
{
"database": "unavailable",
"error": "database ping failed",
"service": "Update Server",
"status": "degraded",
"timestamp": "2026-06-11T10:15:30Z"
}
```
## API Index
### GET /api/v1
Публичный JSON-index текущей версии API. Авторизация не нужна.
```bash
curl -i https://updates.example.com/api/v1
```
Ответ:
```json
{
"auth": {
"header": "Authorization: Bearer <api_key>",
"type": "bearer"
},
"routes": [
{
"description": "List active projects accessible to the API key.",
"method": "GET",
"path": "/api/v1/projects"
},
{
"description": "Get the latest active release metadata for an accessible project.",
"method": "GET",
"path": "/api/v1/projects/{projectSlug}/releases/latest"
},
{
"description": "Get release metadata for an accessible release.",
"method": "GET",
"path": "/api/v1/releases/{releaseID}"
},
{
"description": "Download the private artifact for an accessible release.",
"method": "GET",
"path": "/api/v1/releases/{releaseID}/download"
}
],
"service": "Update Server",
"status": "client-api-ready",
"version": "v1"
}
```
## List Accessible Projects
### GET /api/v1/projects
Возвращает активные проекты, доступные текущему API key.
Требует:
```http
Authorization: Bearer <api_key>
```
Пример:
```bash
curl -i \
-H "Authorization: Bearer upsk_replace_me" \
https://updates.example.com/api/v1/projects
```
Успешный ответ:
```json
{
"projects": [
{
"id": 1,
"name": "Desktop App",
"slug": "desktop-app",
"description": "Primary desktop updater stream.",
"latest_release_url": "/api/v1/projects/desktop-app/releases/latest"
}
]
}
```
Если доступных проектов нет:
```json
{
"projects": []
}
```
## Get Latest Release
### GET /api/v1/projects/{projectSlug}/releases/latest
Возвращает latest active release для активного и доступного проекта.
Latest release выбирается по:
```text
created_at DESC, id DESC
```
Пример:
```bash
curl -i \
-H "Authorization: Bearer upsk_replace_me" \
https://updates.example.com/api/v1/projects/desktop-app/releases/latest
```
Успешный ответ:
```json
{
"project": {
"id": 1,
"name": "Desktop App",
"slug": "desktop-app",
"description": "Primary desktop updater stream.",
"latest_release_url": "/api/v1/projects/desktop-app/releases/latest"
},
"release": {
"id": 2,
"version": "1.1.0",
"build": "build-2",
"filename": "desktop-app-1.1.0.zip",
"checksum_sha256": "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7c6e64b2f7110b2",
"size_bytes": 12345678,
"content_type": "application/zip",
"release_notes": "Improved desktop rollout.",
"created_at": "2026-06-11T10:15:30Z",
"metadata_url": "/api/v1/releases/2",
"download_url": "/api/v1/releases/2/download"
}
}
```
Если проект не существует, архивирован или недоступен этому key:
```http
HTTP/1.1 404 Not Found
```
```json
{
"error": "resource not found"
}
```
Если проект доступен, но активных релизов нет:
```http
HTTP/1.1 404 Not Found
```
```json
{
"error": "release not found"
}
```
## Get Release Metadata
### GET /api/v1/releases/{releaseID}
Возвращает metadata релиза по ID. Релиз должен быть активным, его проект должен
быть активным, и текущий API key должен иметь доступ к проекту релиза.
Пример:
```bash
curl -i \
-H "Authorization: Bearer upsk_replace_me" \
https://updates.example.com/api/v1/releases/2
```
Успешный ответ:
```json
{
"project": {
"id": 1,
"name": "Desktop App",
"slug": "desktop-app",
"description": "Primary desktop updater stream.",
"latest_release_url": "/api/v1/projects/desktop-app/releases/latest"
},
"release": {
"id": 2,
"version": "1.1.0",
"build": "build-2",
"filename": "desktop-app-1.1.0.zip",
"checksum_sha256": "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7c6e64b2f7110b2",
"size_bytes": 12345678,
"content_type": "application/zip",
"release_notes": "Improved desktop rollout.",
"created_at": "2026-06-11T10:15:30Z",
"metadata_url": "/api/v1/releases/2",
"download_url": "/api/v1/releases/2/download"
}
}
```
Если `releaseID` не число:
```http
HTTP/1.1 400 Bad Request
```
```json
{
"error": "invalid release id"
}
```
Если релиз не существует, архивирован или недоступен:
```http
HTTP/1.1 404 Not Found
```
```json
{
"error": "resource not found"
}
```
## Download Release Artifact
### GET /api/v1/releases/{releaseID}/download
Скачивает приватный artifact релиза. Скачивание проходит через приложение, чтобы
сервер мог проверить API key, permission и project scope.
Пример:
```bash
curl -L \
-H "Authorization: Bearer upsk_replace_me" \
-o desktop-app-1.1.0.zip \
https://updates.example.com/api/v1/releases/2/download
```
Успешный ответ:
```http
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=desktop-app-1.1.0.zip
Content-Type: application/zip
X-Content-Type-Options: nosniff
```
Body ответа - bytes загруженного файла.
Если artifact отсутствует на диске:
```http
HTTP/1.1 404 Not Found
```
```json
{
"error": "release not found"
}
```
Если релиз недоступен:
```http
HTTP/1.1 404 Not Found
```
```json
{
"error": "resource not found"
}
```
## Project Object
```json
{
"id": 1,
"name": "Desktop App",
"slug": "desktop-app",
"description": "Primary desktop updater stream.",
"latest_release_url": "/api/v1/projects/desktop-app/releases/latest"
}
```
Поля:
- `id` - numeric project ID.
- `name` - display name.
- `slug` - stable URL identifier.
- `description` - optional description.
- `latest_release_url` - relative URL для latest release lookup.
## Release Object
```json
{
"id": 2,
"version": "1.1.0",
"build": "build-2",
"filename": "desktop-app-1.1.0.zip",
"checksum_sha256": "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7c6e64b2f7110b2",
"size_bytes": 12345678,
"content_type": "application/zip",
"release_notes": "Improved desktop rollout.",
"created_at": "2026-06-11T10:15:30Z",
"metadata_url": "/api/v1/releases/2",
"download_url": "/api/v1/releases/2/download"
}
```
Поля:
- `id` - numeric release ID.
- `version` - версия, введенная администратором при загрузке.
- `build` - build label или build number, если задан.
- `filename` - download filename.
- `checksum_sha256` - SHA-256 загруженного artifact.
- `size_bytes` - размер artifact в bytes.
- `content_type` - MIME type artifact.
- `release_notes` - release notes.
- `created_at` - время загрузки релиза.
- `metadata_url` - relative URL metadata endpoint.
- `download_url` - relative URL download endpoint.
## Ошибки
JSON API возвращает ошибки в формате:
```json
{
"error": "message"
}
```
Основные статусы:
| Status | Когда |
| --- | --- |
| `400` | Некорректный `releaseID`, например не число. |
| `401` | Нет bearer token, token неверный, key выключен или истек. |
| `403` | API key валиден, но нет `can_download`. |
| `404` | Ресурс не найден, архивирован или недоступен текущему key. |
| `429` | Сработал rate limit client API. |
| `500` | Внутренняя ошибка сервера. |
| `503` | Health check: база недоступна; или API key service недоступен. |
### 401 Unauthorized
Missing header:
```http
WWW-Authenticate: Bearer realm="update-server"
```
```json
{
"error": "missing bearer api key"
}
```
Invalid, disabled or expired key:
```json
{
"error": "invalid api key"
}
```
### 403 Forbidden
```json
{
"error": "api key permission denied"
}
```
### 429 Too Many Requests
```http
Retry-After: 3
```
```json
{
"error": "rate limit exceeded"
}
```
По умолчанию client API rate limit настраивается переменными:
```text
APP_CLIENT_RATE_LIMIT_PER_MINUTE=120
APP_CLIENT_RATE_LIMIT_BURST=60
```
## Headers
Для protected API responses сервер добавляет:
```http
Cache-Control: no-store, private, max-age=0
Pragma: no-cache
Expires: 0
Vary: Authorization
X-Robots-Tag: noindex, nofollow
```
Также на все ответы добавляются security headers, включая:
```http
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
```
Если `APP_BASE_URL` использует `https://`, включается:
```http
Strict-Transport-Security: max-age=31536000
```
## Типовой Client Flow
1. Администратор создает проект в `/admin/projects`.
2. Администратор загружает release artifact в проекте.
3. Администратор создает API key в `/admin/api-keys`.
4. Для ключа включает `can_download`.
5. Для ключа выбирает scope, который разрешает доступ к проекту.
6. Клиент запрашивает latest release:
```bash
curl -sS \
-H "Authorization: Bearer upsk_replace_me" \
https://updates.example.com/api/v1/projects/desktop-app/releases/latest
```
7. Клиент сравнивает `release.version` со своей текущей версией.
8. Если версия новая, клиент скачивает `release.download_url`.
9. Клиент проверяет:
- `checksum_sha256`;
- `size_bytes`;
- при необходимости свою подпись artifact, если она есть в продукте.
## Python Example
В репозитории есть минимальный helper:
```text
example/autoupdate.py
example/update_client.py
```
Приватный файл рядом со скриптом:
```json
{
"base_url": "https://updates.example.com",
"api_key": "upsk_replace_me",
"project_slug": "desktop-app"
}
```
Вызов:
```python
from autoupdate import ensure_updated_from_config
ver = "1.0.0"
ensure_updated_from_config(current_version=ver)
```
Если `autoupdate.json` отсутствует, helper создаст файл с placeholder values,
не пойдет в сеть и выведет короткое сообщение о настройке конфига.
## Чего Сейчас Нет В API
На текущий момент client JSON API не предоставляет:
- создание/редактирование проектов;
- загрузку релизов;
- создание/редактирование API keys;
- список всех релизов проекта;
- release channels вроде `stable` / `beta`;
- query parameters вроде `platform`, `arch`, `current_version`;
- публичные downloads без API key.
Эти действия либо выполняются через admin web UI, либо пока являются будущим
расширением API.