update_server/docs/API.md

15 KiB
Raw Permalink Blame History

Update Server API

Этот документ описывает текущий публичный HTTP API сервиса. Его можно передать клиентскому приложению или агенту, который встраивает автообновление.

Примеры ниже используют:

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:

/admin
/admin/projects
/admin/api-keys

Клиентские приложения работают через JSON API:

/api/v1

На текущий момент JSON API предназначен для чтения и скачивания обновлений. Создание проектов, загрузка релизов и создание API keys выполняются через admin web UI, а не через публичный JSON API.

Авторизация

Защищенные endpoint'ы требуют bearer token:

Authorization: Bearer <api_key>

Пример:

curl -i \
  -H "Authorization: Bearer upsk_replace_me" \
  https://updates.example.com/api/v1/projects

API key должен быть:

  • активным;
  • не истекшим по expires_at, если срок задан;
  • с разрешением can_download;
  • со scope, который разрешает доступ к нужному проекту.

Поддерживаемые scope modes:

all_projects
project_allow_list
project_deny_list
tag_allow_list
tag_deny_list

Полный API key показывается в admin UI только один раз при создании. Сервер хранит только hash ключа.

Формат JSON

Все JSON-ответы возвращаются с:

Content-Type: application/json; charset=utf-8

Поля времени возвращаются строкой в формате RFC3339/RFC3339Nano, например:

"created_at": "2026-06-11T10:15:30Z"

Относительные URL в ответах, например download_url, нужно склеивать с Base URL клиента.

Health Check

GET /healthz

Публичная проверка готовности HTTP-сервера и SQLite.

curl -i https://updates.example.com/healthz

Успешный ответ:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
  "database": "ok",
  "service": "Update Server",
  "status": "ok",
  "timestamp": "2026-06-11T10:15:30Z"
}

Если база недоступна:

HTTP/1.1 503 Service Unavailable
{
  "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. Авторизация не нужна.

curl -i https://updates.example.com/api/v1

Ответ:

{
  "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.

Требует:

Authorization: Bearer <api_key>

Пример:

curl -i \
  -H "Authorization: Bearer upsk_replace_me" \
  https://updates.example.com/api/v1/projects

Успешный ответ:

{
  "projects": [
    {
      "id": 1,
      "name": "Desktop App",
      "slug": "desktop-app",
      "description": "Primary desktop updater stream.",
      "latest_release_url": "/api/v1/projects/desktop-app/releases/latest"
    }
  ]
}

Если доступных проектов нет:

{
  "projects": []
}

Get Latest Release

GET /api/v1/projects/{projectSlug}/releases/latest

Возвращает latest active release для активного и доступного проекта.

Latest release выбирается по:

created_at DESC, id DESC

Пример:

curl -i \
  -H "Authorization: Bearer upsk_replace_me" \
  https://updates.example.com/api/v1/projects/desktop-app/releases/latest

Успешный ответ:

{
  "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/1.1 404 Not Found
{
  "error": "resource not found"
}

Если проект доступен, но активных релизов нет:

HTTP/1.1 404 Not Found
{
  "error": "release not found"
}

Get Release Metadata

GET /api/v1/releases/{releaseID}

Возвращает metadata релиза по ID. Релиз должен быть активным, его проект должен быть активным, и текущий API key должен иметь доступ к проекту релиза.

Пример:

curl -i \
  -H "Authorization: Bearer upsk_replace_me" \
  https://updates.example.com/api/v1/releases/2

Успешный ответ:

{
  "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/1.1 400 Bad Request
{
  "error": "invalid release id"
}

Если релиз не существует, архивирован или недоступен:

HTTP/1.1 404 Not Found
{
  "error": "resource not found"
}

Download Release Artifact

GET /api/v1/releases/{releaseID}/download

Скачивает приватный artifact релиза. Скачивание проходит через приложение, чтобы сервер мог проверить API key, permission и project scope.

Пример:

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/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/1.1 404 Not Found
{
  "error": "release not found"
}

Если релиз недоступен:

HTTP/1.1 404 Not Found
{
  "error": "resource not found"
}

Project Object

{
  "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

{
  "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 возвращает ошибки в формате:

{
  "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:

WWW-Authenticate: Bearer realm="update-server"
{
  "error": "missing bearer api key"
}

Invalid, disabled or expired key:

{
  "error": "invalid api key"
}

403 Forbidden

{
  "error": "api key permission denied"
}

429 Too Many Requests

Retry-After: 3
{
  "error": "rate limit exceeded"
}

По умолчанию client API rate limit настраивается переменными:

APP_CLIENT_RATE_LIMIT_PER_MINUTE=120
APP_CLIENT_RATE_LIMIT_BURST=60

Headers

Для protected API responses сервер добавляет:

Cache-Control: no-store, private, max-age=0
Pragma: no-cache
Expires: 0
Vary: Authorization
X-Robots-Tag: noindex, nofollow

Также на все ответы добавляются security headers, включая:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer

Если APP_BASE_URL использует https://, включается:

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:

    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:

example/autoupdate.py
example/update_client.py

Приватный файл рядом со скриптом:

{
  "base_url": "https://updates.example.com",
  "api_key": "upsk_replace_me",
  "project_slug": "desktop-app"
}

Вызов:

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.