Files
wrapped/README.md
T
Sergey Antropoff e4240a7be5 Init
2026-07-17 15:57:36 +03:00

15 KiB
Raw Blame History

Wrapped

Анонимный сервис одноразовой передачи зашифрованных данных (текст, изображения, файлы). Шифрование выполняется в браузере (Web Crypto), на сервере хранится только ciphertext. После первого успешного открытия пакет удаляется из хранилища.

Возможности:

  • zero-knowledge шифрование на клиенте
  • одноразовое открытие (unwrap)
  • опциональный пароль (client_only или server_gate)
  • CAPTCHA: Cloudflare Turnstile и/или hCaptcha
  • админка с лимитами, TTL, allowlist MIME, audit-логом
  • Docker Compose и Helm

Ссылка для получателя: /w/<id>#<key> или токен wrapped_v1.<id>.<key>. Ключ шифрования в URL-фрагменте (#...) на сервер не уходит.

Скриншоты

Интерфейс на русском и английском, светлая и тёмная тема.

Создание wrap

Светлая тема Тёмная тема
Создание — светлая Создание — тёмная

Создание с текстом и файлами

Текст с подсветкой синтаксиса, вложения (изображения, документы), TTL и опциональный пароль.

Светлая тема Тёмная тема
С контентом — светлая С контентом — тёмная

Токен выдан

После создания — ссылка /w/<id>#<key> и wrapped_v1-токен. Ключ только во фрагменте URL.

Светлая тема Тёмная тема
Токен выдан — светлая Токен выдан — тёмная

Unwrap (открытие)

Одноразовое открытие: ciphertext удаляется на сервере, превью — только в текущей сессии браузера.

Светлая тема Тёмная тема
Unwrap — светлая Unwrap — тёмная

Быстрый старт (разработка)

Нужны Docker и Docker Compose.

make env          # создаёт .env из .env.example
make up           # app + Postgres + MinIO + nginx
Сервис URL
Приложение http://localhost:8000
Админка http://localhost:8000/admin
MinIO Console http://localhost:9001 (wrappedminio / wrappedminio123)
Postgres (опционально с хоста) 127.0.0.1:5433
make logs         # логи app
make down         # остановить
make clean        # остановить и удалить volumes

Локальный docker-compose.yml собирает образ из Dockerfile и монтирует код с --reload — это режим разработки. Для сервера используйте готовый образ с Docker Hub (см. ниже).


Запуск на сервере (Docker Hub + Compose)

Образ приложения публикуется на Docker Hub. На сервере его не нужно собирать — достаточно docker compose pull / up.

1. Подготовка

Создайте каталог и файлы:

mkdir -p /opt/wrapped && cd /opt/wrapped

Создайте .env (обязательно смените секреты):

# Приложение
APP_NAME=Wrapped
APP_ENV=production
APP_SECRET_KEY=замените-на-длинную-случайную-строку
APP_BASE_URL=https://wrapped.example.com
LOG_LEVEL=INFO
DOCS_ENABLED=false

# Админ
ADMIN_USERNAME=admin
ADMIN_PASSWORD=замените-пароль

# БД и S3 задаются в compose (см. ниже). Для внешнего Postgres/MinIO
# переопределите DATABASE_URL и S3_* здесь или в environment сервиса app.

# CAPTCHA (опционально; site keys также можно задать в админке)
TURNSTILE_SECRET_KEY=
HCAPTCHA_SECRET_KEY=

# Доверие к прокси для реального IP клиента
TRUSTED_PROXIES=127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16

APP_PORT=8000

2. docker-compose.yml для продакшена

Образ на Docker Hub: inecs/wrapped (тег задайте нужный, например 0.1.0).

services:
  proxy:
    image: nginx:1.27-alpine
    container_name: wrapped-proxy
    restart: unless-stopped
    ports:
      - "${APP_PORT:-8000}:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app

  app:
    # Образ с Docker Hub — не build:
    image: inecs/wrapped:0.1.0
    container_name: wrapped-app
    restart: unless-stopped
    expose:
      - "8000"
    env_file:
      - .env
    environment:
      DATABASE_URL: postgresql+asyncpg://wrapped:CHANGE_DB_PASSWORD@postgres:5432/wrapped
      S3_ENDPOINT_URL: http://minio:9000
      S3_ACCESS_KEY: CHANGE_MINIO_USER
      S3_SECRET_KEY: CHANGE_MINIO_PASSWORD
      S3_BUCKET: wrapped
      S3_USE_SSL: "false"
      S3_CREATE_BUCKET: "true"
      APP_BASE_URL: ${APP_BASE_URL:-http://localhost:8000}
      APP_ENV: production
      DOCS_ENABLED: "false"
      TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
    depends_on:
      postgres:
        condition: service_healthy
      minio:
        condition: service_started
      minio-init:
        condition: service_completed_successfully
    # Миграции и старт уже в CMD образа:
    # alembic upgrade head && uvicorn ...

  postgres:
    image: postgres:16-alpine
    container_name: wrapped-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: wrapped
      POSTGRES_PASSWORD: CHANGE_DB_PASSWORD
      POSTGRES_DB: wrapped
    volumes:
      - wrapped_pg_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U wrapped -d wrapped"]
      interval: 5s
      timeout: 5s
      retries: 10

  minio:
    image: minio/minio:latest
    container_name: wrapped-minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: CHANGE_MINIO_USER
      MINIO_ROOT_PASSWORD: CHANGE_MINIO_PASSWORD
    volumes:
      - wrapped_minio_data:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 5s
      retries: 5

  minio-init:
    image: minio/mc:latest
    container_name: wrapped-minio-init
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
      mc alias set local http://minio:9000 CHANGE_MINIO_USER CHANGE_MINIO_PASSWORD &&
      mc mb --ignore-existing local/wrapped &&
      exit 0
      "

volumes:
  wrapped_pg_data:
  wrapped_minio_data:

Рядом положите nginx.conf (прокси на app):

server {
    listen 80;
    server_name _;

    client_max_body_size 64m;

    location / {
        proxy_pass http://app:8000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_read_timeout 120s;
    }
}

Готовый пример конфига также есть в репозитории: deploy/nginx.conf.

3. Запуск

docker compose pull
docker compose up -d
docker compose ps
curl -fsS http://127.0.0.1:8000/health

Обновление на новую версию образа:

# в .env / compose поменяйте тег, например inecs/wrapped:0.2.0
docker compose pull app
docker compose up -d app

HTTPS лучше завернуть снаружи (Caddy, Traefik, nginx на хосте, Cloudflare Tunnel) и проксировать на ${APP_PORT}. В APP_BASE_URL укажите публичный https://... URL.

Внешний Postgres и S3/MinIO

Если БД и объектное хранилище уже есть, уберите сервисы postgres, minio, minio-init из compose и задайте:

Переменная Пример
DATABASE_URL postgresql+asyncpg://user:pass@db-host:5432/wrapped
S3_ENDPOINT_URL https://s3.example.com
S3_ACCESS_KEY / S3_SECRET_KEY ключи
S3_BUCKET wrapped
S3_USE_SSL true
S3_CREATE_BUCKET false (если бакет уже создан)
S3_REGION us-east-1

Минимальный compose в этом случае — только app (+ опционально proxy):

services:
  app:
    image: inecs/wrapped:0.1.0
    restart: unless-stopped
    ports:
      - "8000:8000"
    env_file:
      - .env
    environment:
      APP_ENV: production
      DOCS_ENABLED: "false"

Переменные окружения

Переменная Описание По умолчанию
APP_NAME Имя сервиса Wrapped
APP_ENV development / production development
APP_SECRET_KEY Секрет сессий/подписей — смените
APP_BASE_URL Публичный URL (ссылки, редиректы) http://localhost:8000
DOCS_ENABLED /docs, /redoc, /openapi.json в prod лучше false
LOG_LEVEL Уровень логов INFO
ADMIN_USERNAME Логин админки admin
ADMIN_PASSWORD Пароль админки — смените
DATABASE_URL Postgres (asyncpg)
S3_ENDPOINT_URL Endpoint MinIO/S3
S3_ACCESS_KEY Access key
S3_SECRET_KEY Secret key
S3_BUCKET Имя бакета wrapped
S3_REGION Регион us-east-1
S3_USE_SSL TLS к S3 false
S3_CREATE_BUCKET Создавать бакет при старте true
TURNSTILE_SECRET_KEY Секрет Turnstile пусто
HCAPTCHA_SECRET_KEY Секрет hCaptcha пусто
TRUSTED_PROXIES IP/CIDR прокси для X-Forwarded-* loopback + RFC1918

Полный шаблон: .env.example.


Модель безопасности

  • Zero-knowledge. Шифрование AES-GCM в браузере. Сервер видит только ciphertext и метаданные (TTL, MIME, размер).
  • Одноразовое открытие. После успешного unwrap объект удаляется из S3/MinIO, статус wrap → consumed.
  • Пароль. Режим client_only — пароль участвует в ключе на клиенте; server_gate — сервер проверяет хеш (Argon2) до выдачи ciphertext.
  • CAPTCHA. Включается в админке; секреты — через env, site keys — в UI.
  • IP в audit. Реальный IP берётся из заголовков только от TRUSTED_PROXIES.

Ключ в #fragment не отправляется на сервер в HTTP-запросе страницы.


Админка

  • UI: /admin (логин /admin/login)
  • Настройки: лимиты загрузки, TTL, rate limit, MIME allowlist, CAPTCHA, режим пароля, retention audit
  • Audit-лог и опасные операции (purge) — в соответствующих разделах UI
  • JSON Admin API: Basic Auth (ADMIN_USERNAME / ADMIN_PASSWORD), тег OpenAPI Admin

API (автоматизация)

Тот же ZK-протокол, что и в UI: сначала шифруете локально, затем:

Метод Путь Назначение
GET /api/v1/settings Публичные лимиты, CAPTCHA, режим пароля
POST /api/v1/wraps Загрузить ciphertext + метаданные
POST /api/v1/wraps/{id}/unwrap Одноразово получить ciphertext
GET /health Healthcheck

Серверного endpoint «зашифруй за меня» нет.

При DOCS_ENABLED=true доступны Swagger /docs и OpenAPI /openapi.json.


Сборка и публикация образа

Нужен логин в Docker Hub: docker login (пользователь inecs).

# Сборка + push на hub.docker.com как inecs/wrapped:0.1.0
make release IMAGE_TAG=0.1.0

# Только собрать и затегать локально (без push)
make release IMAGE_TAG=0.1.0 PUSH=0

По умолчанию: RELEASE_REGISTRY=inecs, IMAGE_TAG=0.1.0 → образ inecs/wrapped:0.1.0.

Эквивалент вручную:

docker build -t inecs/wrapped:0.1.0 .
docker push inecs/wrapped:0.1.0

Образ при старте сам выполняет alembic upgrade head и поднимает uvicorn на порту 8000. Healthcheck: GET /health.


Kubernetes (Helm)

helm upgrade --install wrapped ./helm/wrapped \
  -f my-values.yaml

В values.yaml задайте образ с Hub, внешние DB/S3 и секреты:

image:
  repository: inecs/wrapped
  tag: "0.1.0"
  pullPolicy: IfNotPresent

app:
  env: production
  baseUrl: "https://wrapped.example.com"
  secretKey: "..."
  adminUsername: admin
  adminPassword: "..."
  docsEnabled: false
  trustedProxies: "*"   # если до пода достучаться может только Ingress

external:
  databaseUrl: "postgresql+asyncpg://..."
  s3:
    endpointUrl: "https://..."
    accessKey: "..."
    secretKey: "..."
    bucket: wrapped
    createBucket: false

Упаковка чарта: make helm-package IMAGE_TAG=0.1.0.


Make-цели (разработка)

Цель Описание
make env .env из .env.example
make up Поднять dev-стек
make down / make clean Остановить / с volumes
make logs / make ps Логи / статус
make migrate alembic upgrade head
make release Сборка образа (+ push) и Helm package
make helm-lint / make helm-package Helm

Требования

  • Python 3.12+ (для запуска без Docker)
  • PostgreSQL 16+
  • S3-совместимое хранилище (MinIO и т.п.)
  • Docker Compose v2 — для деплоя как выше

Лицензия

Proprietary / на ваше усмотрение.