backupy-agent/docs/07-api-contract.md
TronoSfera 8b0c978337 feat(initial): Backupy agent + backupy-decrypt CLI
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/        — Go agent (WSS client, persistent queue, Docker
                       discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
                       pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/    — protobuf wire format (generated .pb.go committed
                       so the repo builds without protoc)
- docs/              — agent spec + wire-protocol contract

Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).
2026-05-17 20:22:35 +03:00

21 KiB
Raw Blame History

07. API-контракты

1. Транспорт Agent ↔ Server

  • Протокол: WebSocket Secure (WSS) на :443, бинарные фреймы с Protobuf-payload'ами.
  • Альтернатива для Phase 3+: gRPC bidirectional stream.
  • Длинноживущее соединение, инициируемое агентом.
  • Heartbeat: PING каждые 30 секунд, RTO 90 секунд.
  • Reconnect: exponential backoff 1s → 2s → 4s → … → 60s cap, jitter ±20%.

2. Аутентификация WSS

  1. Агент открывает WSS-соединение с заголовком Authorization: Bearer <agent_key>.
  2. Сервер валидирует key (argon2-хэш в БД), достаёт agent_id.
  3. Первое сообщение от агента — Register, сервер отвечает RegisterAck с config snapshot и assigned session_id.
  4. Все последующие сообщения содержат monotonic seq для дедупликации.

3. Envelope

syntax = "proto3";
package backup.v1;

message Envelope {
  uint64 seq = 1;              // monotonic, per-direction
  uint64 ts_ms = 2;            // unix ms, отправителя
  string correlation_id = 3;   // для request/response пар
  oneof payload {
    // ----- agent → server -----
    Register register = 10;
    Heartbeat heartbeat = 11;
    DiscoveryReport discovery = 12;
    JobUpdate job_update = 13;
    BackupCompleted backup_completed = 14;
    HealthCheckResult health_result = 15;
    LogEvent log = 16;
    RestoreUpdate restore_update = 17;
    Ack ack = 18;

    // ----- server → agent -----
    RegisterAck register_ack = 50;
    ConfigUpdate config_update = 51;
    RunBackup run_backup = 52;
    CancelJob cancel_job = 53;
    RunHealthCheck run_health_check = 54;
    SelfUpdate self_update = 56;
    Ping ping = 57;
  }
}

Изменение относительно v1: RunRestore удалён из MVP. В Phase 1 нет restore-в-БД, только download через REST endpoint (GET /runs/:id/download-url + локальная расшифровка CLI-утилитой). См. 04-server-spec.md → Download.

4. Сообщения Agent → Server

Register

message Register {
  string agent_version = 1;
  string hostname = 2;
  string os = 3;                    // "linux"
  string arch = 4;                  // "amd64"
  string docker_version = 5;
  repeated string capabilities = 6; // ["pg_dump", "mysqldump", "docker_discovery"]
  uint64 last_known_config_version = 7;
}

Heartbeat

message Heartbeat {
  uint64 config_version = 1;
  AgentMetrics metrics = 2;
  repeated string active_job_ids = 3;
}
message AgentMetrics {
  float cpu_percent = 1;
  uint64 mem_used_bytes = 2;
  uint64 mem_total_bytes = 3;
  uint64 disk_used_bytes = 4;
  uint64 disk_total_bytes = 5;
  uint32 queue_depth = 6;
}

Частота: каждые 30 секунд.

DiscoveryReport

message DiscoveryReport {
  repeated DiscoveredContainer containers = 1;
}
message DiscoveredContainer {
  string container_id = 1;
  string name = 2;
  string image = 3;              // "postgres:16"
  string detected_db_type = 4;   // "postgresql"
  repeated string networks = 5;
  map<string,string> env_hints = 6; // отфильтрованные env без секретов
  repeated PortBinding ports = 7;
}
message PortBinding {
  uint32 container_port = 1;
  uint32 host_port = 2;
  string protocol = 3;
}

Отправляется при старте и при изменениях docker events.

JobUpdate

message JobUpdate {
  string job_id = 1;
  JobStatus status = 2;
  uint32 progress_percent = 3;
  string current_step = 4;        // "dumping", "compressing", "uploading"
  string error_message = 5;       // если status=FAILED
}
enum JobStatus {
  JOB_STATUS_UNSPECIFIED = 0;
  QUEUED = 1;
  RUNNING = 2;
  SUCCESS = 3;
  FAILED = 4;
  CANCELLED = 5;
}

BackupCompleted

message BackupCompleted {
  string job_id = 1;
  string run_id = 2;
  string s3_key = 3;
  uint64 size_bytes = 4;
  string sha256 = 5;
  uint64 duration_ms = 6;
  string dek_kms_id = 7;
  bytes encrypted_dek = 8;
  string compression = 9;         // "zstd"
  string db_engine_version = 10;  // "PostgreSQL 16.2"
}

HealthCheckResult

message HealthCheckResult {
  string check_id = 1;
  uint64 ts_ms = 2;
  bool ok = 3;
  uint32 latency_ms = 4;
  string error = 5;
  uint32 status_code = 6;         // для HTTP
}

LogEvent

message LogEvent {
  uint64 ts_ms = 1;
  LogLevel level = 2;
  string job_id = 3;              // опционально
  string message = 4;
  map<string,string> fields = 5;
}
enum LogLevel { TRACE=0; DEBUG=1; INFO=2; WARN=3; ERROR=4; }

RestoreUpdate

message RestoreUpdate {
  string restore_id = 1;
  JobStatus status = 2;
  uint32 progress_percent = 3;
  string current_step = 4;
  string error_message = 5;
}

Ack

message Ack {
  string correlation_id = 1;
  bool accepted = 2;
  string reason = 3;              // если accepted=false
}

5. Сообщения Server → Agent

RegisterAck

message RegisterAck {
  string session_id = 1;
  AgentConfig config = 2;
  uint32 heartbeat_interval_sec = 3;
  string server_time_iso = 4;
}

AgentConfig (используется в RegisterAck и ConfigUpdate)

message AgentConfig {
  uint64 version = 1;
  repeated Target targets = 2;
  repeated BackupJobSpec jobs = 3;
  repeated HealthCheckSpec health_checks = 4;
  MaintenanceWindow maintenance = 5;
  AdvancedSettings advanced = 6;
}

message Target {
  string id = 1;
  DbType type = 2;
  string display_name = 3;
  ConnectionConfig connection = 4;
}

enum DbType {
  DB_UNSPECIFIED = 0;
  POSTGRESQL = 1;
  MYSQL = 2;
  MARIADB = 3;
  MONGODB = 4;
  REDIS = 5;
  SQLITE = 6;
}

message ConnectionConfig {
  string host = 1;
  uint32 port = 2;
  string database = 3;
  string username = 4;
  string password_secret_ref = 5; // ссылка на зашифрованный секрет
  string container_id = 6;         // для docker exec strategy
  ConnectionStrategy strategy = 7;
}
enum ConnectionStrategy { TCP = 0; DOCKER_EXEC = 1; UNIX_SOCKET = 2; }

message BackupJobSpec {
  string id = 1;
  string target_id = 2;
  string cron = 3;                 // UTC
  uint32 retention_days = 4;
  string compression = 5;          // "zstd" | "gzip" | "none"
  bool encryption_enabled = 6;
  repeated string pre_hooks = 7;
  repeated string post_hooks = 8;
  RetryPolicy retry = 9;
  uint32 timeout_sec = 10;
}

message RetryPolicy {
  uint32 max_attempts = 1;
  repeated uint32 backoff_seconds = 2;  // [60, 300, 1800]
}

message HealthCheckSpec {
  string id = 1;
  string name = 2;
  CheckType type = 3;
  string target = 4;               // URL или host:port
  uint32 interval_sec = 5;
  uint32 timeout_sec = 6;
  uint32 expected_status = 7;      // для HTTP
}
enum CheckType { HTTP = 0; HTTPS = 1; TCP = 2; }

message MaintenanceWindow {
  string start_utc = 1;            // "02:00"
  string end_utc = 2;              // "05:00"
}

message AdvancedSettings {
  uint32 max_parallel_jobs = 1;
  string log_level = 2;
  bool auto_update_enabled = 3;
}

RunBackup

message RunBackup {
  string job_id = 1;
  string run_id = 2;
  bool manual_trigger = 3;
  S3UploadCreds upload_creds = 4;
  bytes encrypted_dek = 5;         // DEK для шифрования, обёрнутый KMS
}
message S3UploadCreds {
  string presigned_put_url = 1;
  uint64 expires_at_ms = 2;
  string final_s3_key = 3;
}

CancelJob, RunHealthCheck

message CancelJob { string job_id = 1; }
message RunHealthCheck { string check_id = 1; }

RunRestore — удалено в MVP

В Phase 1 функция «получить данные обратно» работает через REST API (см. ниже эндпоинты /runs/:id/download-url и /runs/:id/decryption-token) + локальную CLI-утилиту backupy-decrypt. Агент в этом потоке не участвует.

Restore-в-БД через агент (RESTORE_MODE: NEW_DATABASE / ANOTHER_AGENT) — Phase 3. К тому моменту в proto будет добавлен новый message RunRestore (proto v2).

SelfUpdate

message SelfUpdate {
  string target_version = 1;
  string binary_url = 2;
  string sha256 = 3;
  bytes cosign_signature = 4;
  bool force = 5;
}

6. REST API

База: https://api.backupy.ru/v1. Auth: Authorization: Bearer <token> (session-token из UI или API-key).

Auth & Account

Метод Путь Назначение
POST /auth/signup email+password регистрация
POST /auth/login email+password
POST /auth/magic-link/request запрос magic link
GET /auth/magic-link/verify?token= подтверждение
GET /auth/oauth/github OAuth redirect
GET /auth/oauth/github/callback OAuth callback
GET /auth/oauth/google OAuth redirect
GET /auth/oauth/google/callback OAuth callback
POST /auth/logout
POST /auth/password-reset/request
POST /auth/password-reset/confirm
GET /me текущий пользователь
PATCH /me обновить профиль
DELETE /me account deletion (start grace period)
GET /me/export data export JSON
POST /me/2fa/enable включить TOTP
POST /me/2fa/disable

Agents

Метод Путь Назначение
GET /agents список агентов
POST /agents создать (возвращает раз secret key)
GET /agents/:id детали
PATCH /agents/:id изменить имя/настройки
DELETE /agents/:id
POST /agents/:id/rotate-key новый ключ
GET /agents/:id/config текущий config (видимый пользователю)
PATCH /agents/:id/config изменить (создаёт новую version)
GET /agents/:id/discovered то, что обнаружил Docker socket
GET /agents/:id/metrics последние метрики

Targets & Jobs

Метод Путь Назначение
GET /agents/:id/targets список target'ов
POST /agents/:id/targets создать target
PATCH /targets/:id
DELETE /targets/:id
POST /targets/:id/test-connection проверка коннекта
GET /agents/:id/jobs jobs
POST /agents/:id/jobs новый job
GET /jobs/:id детали
PATCH /jobs/:id редактирование
DELETE /jobs/:id
POST /jobs/:id/run manual run, Idempotency-Key поддерживается

Backup Runs

Метод Путь Назначение
GET /runs каталог с фильтрами ?agent_id=&target_id=&from=&to=&status=
GET /runs/:id детали
GET /runs/:id/download-url presigned GET URL для скачивания зашифрованного объекта (TTL 15 min)
POST /runs/:id/decryption-token временный JWT с plaintext DEK для CLI-утилиты (TTL 15 min)
DELETE /runs/:id удалить бэкап вручную

Restore-в-БД (POST /runs/:id/restore + /restores/:id) — Phase 3, не реализуется в MVP.

Health Checks

Метод Путь Назначение
GET /health-checks
POST /health-checks
PATCH /health-checks/:id
DELETE /health-checks/:id
GET /health-checks/:id/results history с временным фильтром
GET /health-checks/:id/uptime?period=24h|7d|30d|90d aggregated

Storage Profiles

Метод Путь Назначение
GET /storage-profiles managed + BYO
POST /storage-profiles добавить BYO-S3
PATCH /storage-profiles/:id
DELETE /storage-profiles/:id
POST /storage-profiles/:id/test тестовое PUT/GET
GET /storage-profiles/usage сколько занято

Status Page

Метод Путь Назначение
GET /status-page конфиг страницы
PATCH /status-page
POST /status-page/verify-dns проверка CNAME
POST /status-page/incidents создать incident
PATCH /incidents/:id обновить status (Investigating → Resolved)
GET /incidents список
GET /status-page/subscribers

Public (без auth)

Метод Путь Назначение
GET /public/status/:subdomain публичная страница
GET /public/status/:subdomain/rss RSS feed
POST /public/status/:subdomain/subscribe подписка
GET /public/status/:subdomain/unsubscribe?token=

Notifications

Метод Путь Назначение
GET /notification-channels
POST /notification-channels email/telegram/webhook
PATCH /notification-channels/:id
DELETE /notification-channels/:id
POST /notification-channels/:id/test отправить тестовое
GET /notification-preferences
PATCH /notification-preferences

Audit Log & API Tokens

Метод Путь Назначение
GET /audit-log с фильтрами
GET /audit-log/export CSV
GET /api-tokens список (без plaintext)
POST /api-tokens создать
DELETE /api-tokens/:id

6a. Admin REST API (/admin/v1/)

Базовый путь: https://api.backupy.ru/admin/v1. Auth: тот же Bearer token, но проверяется role=admin. Не-admin запрос → 403 PERMISSION_DENIED.

Companies (admin only)

Метод Путь Назначение Phase
GET /companies список с фильтрами ?plan=&status=&country=&search= 1
POST /companies создать вручную (для enterprise-онбординга) 1
GET /companies/:id детальная карточка (users, agents count, payment history placeholder, internal notes) 1
PATCH /companies/:id обновить (имя, контактный email, страна, plan, status) 1
POST /companies/:id/suspend приостановить — все агенты компании перестают получать команды 1
POST /companies/:id/reactivate вернуть в active 1
POST /companies/:id/impersonate создать временную user-session под owner-юзером компании 1
GET /companies/:id/notes internal notes (admin-only) 1
POST /companies/:id/notes добавить заметку 1
POST /companies/:id/change-plan сменить план (без биллинга — биллинг Phase 3) 2

Users (admin only)

Метод Путь Назначение Phase
GET /users все юзеры с фильтрами ?company_id=&role=&status=&search= 1
POST /users создать юзера в конкретной компании (или с auto-создаваемой) 1
GET /users/:id детали юзера 1
POST /users/:id/suspend заблокировать sign-in и API 1
POST /users/:id/reactivate 1
POST /users/:id/impersonate временная user-session 1
POST /users/:id/reset-password отправить reset-email 1
DELETE /users/:id начать GDPR-удаление (полный flow — Phase 3) 1 (start) / 3 (full)

Plans (admin only)

Метод Путь Назначение Phase
GET /plans все тарифы (active + inactive) 1
POST /plans создать новый 2
PATCH /plans/:id обновить (цена, лимиты, фичи, active) 2
DELETE /plans/:id удалить (если customers=0; иначе ошибка) 2
GET /plans/distribution сколько customers на каждом плане + MRR 2

Infrastructure (admin only, read-only)

Метод Путь Назначение Phase
GET /infra/s3 список наших S3 bucket'ов: размер, объекты, стоимость 1
GET /infra/kms KMS keys active, operations today, cost 1
GET /infra/cluster K8s cluster CPU/memory 1
GET /infra/db PostgreSQL size, replication lag, connections 1
GET /infra/redis Redis memory usage 1
GET /infra/cost-breakdown детализация месячных расходов + gross margin расчёт 2

Admin audit log

Метод Путь Назначение Phase
GET /admin-audit список admin-actions с фильтрами 1
GET /admin-audit/export CSV 2

System settings (admin only)

Метод Путь Назначение Phase
GET /system-settings branding, signup config, feature flags 2
PATCH /system-settings обновить 2
GET /system-settings/feature-flags список фич с per-company overrides 2
POST /system-settings/feature-flags/:flag/enable включить глобально 2
POST /system-settings/feature-flags/:flag/disable выключить глобально 2

Admin team

Метод Путь Назначение Phase
GET /admin-team список админов с ролями 2
POST /admin-team/invite пригласить нового админа 2
PATCH /admin-team/:id сменить роль (super_admin / support / billing) 2
DELETE /admin-team/:id удалить админа 2

Payments & Revenue (Phase 3, заглушки в Phase 2)

Метод Путь Назначение Phase
GET /payments все транзакции с фильтрами 3
POST /payments/:id/refund refund-action 3
POST /payments/:id/retry повторить failed charge 3
GET /analytics/revenue MRR, ARR, churn, conversion, cohorts 3

7. Модель ошибок

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Agent not found",
    "details": { "agent_id": "agt_..." },
    "trace_id": "01HM..."
  }
}

Коды:

  • AUTH_FAILED
  • PERMISSION_DENIED
  • RESOURCE_NOT_FOUND
  • VALIDATION_ERROR
  • RATE_LIMITED
  • CONFLICT
  • PRECONDITION_FAILED
  • INTERNAL
  • SERVICE_UNAVAILABLE

8. Идемпотентность

  • POST /jobs/:id/run и POST /runs/:id/restore принимают Idempotency-Key header.
  • Сервер кеширует ответ по ключу на 24 часа.
  • BackupCompleted от агента дедуплицируется по run_id на сервере (upsert).

9. Pagination

  • Query params: ?limit=50&cursor=<opaque>.
  • Response:
{
  "items": [...],
  "next_cursor": "...",
  "has_more": true
}
  • Limit max: 100.

10. Real-time (для UI)

  • SSE endpoint: GET /events?topics=jobs,agents,restores (auth required).
  • События:
    • agent.connected, agent.disconnected, agent.config.applied
    • job.started, job.progress, job.completed, job.failed
    • restore.started, restore.progress, restore.completed, restore.failed
    • incident.opened, incident.resolved

11. Версионирование

  • API: /v1/.... Breaking changes → /v2/.
  • Proto: семантическое версионирование пакета (backup.v1, backup.v2).
  • Агент шлёт agent_version, сервер договаривается о минимальной поддерживаемой версии.
  • Минимум 6 месяцев backward compatibility между proto-версиями.

12. Rate limits (defaults)

Endpoint Limit
/auth/login 5/min/IP
/auth/signup 3/hour/IP
/auth/magic-link/request 3/hour/email
/auth/password-reset/request 3/hour/email
API (auth required) 60/min/token
/public/status/* 60/min/IP

Превышение → 429 с Retry-After.