hybrids3: Stocare de Obiecte Compatibilă S3 Care Nu Cere Doctorat

Pune nginx în fața lui MinIO pe un prefix de cale și încearcă să folosești URL-uri presemnate. Hai, dă-i drumul, aștept.
Problema e AWS Signature V4. Semnătura acoperă calea cererii. nginx pe /storage/ taie prefixul ăla înainte să trimită mai departe, deci boto3 semnează /storage/bucket/key, serverul vede /bucket/key, HMAC-ul nu se potrivește, primești 403. Asta nu e o greșeală de configurare, așa funcționează proxy-ul pe prefix de cale din nginx și așa funcționează verificarea SigV4. Sunt incompatibile structural, dacă nu te prinzi la șmecherii în configul de proxy sau nu treci la un server care tratează treaba altfel.
docker-hybrids3 o tratează altfel. Are o opțiune de config path_prefix, o setezi pe /storage și toate rutele se mută nativ sub prefixul ăla. Fără tăiere de cale la proxy. boto3 semnează /storage/bucket/key, nginx trimite /storage/bucket/key, serverul vede /storage/bucket/key. Semnăturile se potrivesc. Ăsta e și motivul pentru care rulez totul în spatele unui singur gateway nginx (aigate face asta) fără să mă lupt cu configul de proxy.
Cealaltă chestie care m-a împins să îl construiesc: când un agent AI are nevoie să scrie un fișier și să îți întindă înapoi un URL, nu vrei să ridici un object store distribuit pentru asta. Vrei ceva care pornește în două minute și îți iese din drum. HybridS3 are un server MCP încorporat, agentul cheamă upload_object, primește înapoi un URL, gata.

Ce Este

SQLite pentru metadate, fișiere plate pe disc. boto3 merge cu el. Merge și HTTP simplu cu curl. Autentificare cu bearer token, fără ceremonia SigV4 pentru cereri HTTP simple. Trei interfețe, un container, fără consolă web, fără IAM, fără mod distribuit.
Bucketurile se definesc în config, nu se creează prin API. Știi mereu exact ce există. Vrei un bucket? Îl adaugi în YAML, repornești. Vrei expirare TTL? Setezi ttl: 24h pe bucket și obiectele se șterg singure după ultima lor scriere. Fără politici de ciclu de viață, fără joburi de cron.

Cum Îl Rulezi

docker run -d --name hybrids3 
    -p 8080:8080 
    -v ./config.yaml:/config/config.yaml:ro 
    -v hybrids3-data:/data 
    psyb0t/hybrids3

Configul la /config/config.yaml, datele la /data, portul 8080. Rulează cu UID 1000.

Config

master_key: "change-me-to-something-secret"
master_public_key: "master"
cleanup_interval: 1m
# path_prefix: /storage
buckets:
  uploads:
    public: true
    key: "uploads-secret"
    public_key: "uploads-id"
    ttl: 24h
    max_file_size: 50MB
  permanent:
    public: false
    key: "perm-secret"
    public_key: "permanent-id"
    ttl: 0
    max_file_size: 100MB

Fiecare bucket are o cheie privată key (niciodată transmisă, folosită ca să verifice HMAC-urile) și o public_key (ID-ul cheii de acces S3, sigur de pus în URL-uri). Despicătura aia e ce face URL-urile presemnate să funcționeze: cheia publică poate apărea în URL, cea privată niciodată.
public: true înseamnă că GET, HEAD și LIST nu cer autentificare. PUT și DELETE tot au nevoie de o cheie. public: false înseamnă că totul cere autentificare.

API HTTP

Bearer token simplu în headerul Authorization:

# upload
curl -X PUT https://ciprian.51k.eu80/uploads/file.txt 
  -H "Authorization: Bearer uploads-secret" 
  -d "hello"
# read from public bucket — no auth
curl https://ciprian.51k.eu80/uploads/file.txt
# read from private bucket
curl https://ciprian.51k.eu80/permanent/doc.pdf 
  -H "Authorization: Bearer perm-secret"
# list objects with prefix filter
curl "https://ciprian.51k.eu80/uploads?prefix=images/&max-keys=50" 
  -H "Authorization: Bearer uploads-secret"

Cererile cu un header Authorization de tip AWS Sig V4 primesc răspunsuri XML compatibile cu S3. Tot restul primește JSON.

boto3

import boto3
from botocore.config import Config
s3 = boto3.client(
    "s3",
    endpoint_url="https://ciprian.51k.eu80",
    aws_access_key_id="uploads-id",         # public_key from config
    aws_secret_access_key="uploads-secret",  # key from config
    region_name="us-east-1",
    config=Config(signature_version="s3v4"),
)
s3.put_object(Bucket="uploads", Key="file.txt", Body=b"hello")
s3.get_object(Bucket="uploads", Key="file.txt")
s3.list_objects_v2(Bucket="uploads", Prefix="images/")

În Spatele Nginx

Partea care chiar contează. Setezi path_prefix: /storage în config, apoi:

location /storage {
    proxy_pass http://hybrids3:8080;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Două chestii de nimerit: fără slash la final pe proxy_pass (un slash la final îi spune lui nginx să taie prefixul locației, ceea ce strică SigV4) și $http_host, nu $host ($host taie portul, ceea ce provoacă nepotriviri de semnătură pe porturi nestandard).
Cu path_prefix setat, boto3 se îndreaptă spre http://yourdomain/storage și totul merge.

MCP

Un server MCP rulează la /mcp/. Șapte unelte: upload_object, download_object, delete_object, list_objects, list_buckets, object_info, presign_url. Conectezi orice client compatibil MCP:

{
  "mcpServers": {
    "hybrids3": {
      "type": "streamable-http",
      "url": "https://ciprian.51k.eu80/mcp/"
    }
  }
}

Autentificare la nivel de endpoint (header Authorization sau parametru de query ?auth=) sau per unealtă, prin parametrul auth_key. Folosești cheia master pentru acces complet, o cheie de bucket ca să limitezi conexiunea la un singur bucket.

URL-uri Presemnate

Pentru bucketuri private, asta generează un URL presemnat AWS Sig V4 adevărat, semnat cu cheia privată a bucketului:

curl -X POST "https://ciprian.51k.eu80/presign/permanent/doc.pdf?expires=3600" 
  -H "Authorization: Bearer perm-secret"

Pentru bucketuri publice întoarce URL-ul simplu, nu e nevoie de semnătură, pentru că GET nu cere oricum autentificare. Intervalul de expirare e de la o secundă la șapte zile, implicit 3600. URL-urile expirate sau umblate întorc 403.
PUT presemnat. De la v0.2.0, ?method=PUT îi întinde cuiva un URL care îl lasă să încarce o cheie anume fără să îți vadă vreodată cheia de bucket. Bucketurile publice nu sunt o scurtătură aici: citirile anonime sunt permise, scrierile anonime niciodată, deci un PUT presemnat e mereu semnat, chiar și pe un bucket public.

# generate an upload URL
curl -X POST "https://ciprian.51k.eu80/presign/uploads/inbox/report.pdf?method=PUT&expires=600" 
  -H "Authorization: Bearer uploads-secret"
# → {"url": "...X-Amz-Signature=...", "method": "PUT", "expires": 600}
# upload with the URL alone — no Authorization header
curl -X PUT "<url>" --data-binary @report.pdf

Un URL presemnat e legat de verbul lui. Un URL de GET nu poate fi folosit pentru PUT și invers, metoda face parte din cererea canonică pe care o acoperă semnătura. Ceea ce e tot rostul: poți întinde unui terț o cutie de livrare pentru exact o cheie, pentru exact zece minute, iar el tot nu poate citi altceva și nici nu poate încărca altundeva.
Unealta MCP presign_url ia același parametru method, deci un agent poate bate și el URL-uri de încărcare.

Buildul e închis cu lacăt

Lanțul de aprovizionare a fost blocat pe hash cu uv, cu o barieră de vârstă la dată fixă, deci o dependință publicată ieri nu se poate strecura în buildul de azi. Dockerfile de producție multi-stage, imaginea de bază și uv fixate pe digest, nu urmărite pe tag.
Makefile-ul e singurul punct de intrare, iar tot ce ține de dezvoltare rulează într-un container de dev izolat. Nu se instalează nimic pe host-ul tău. 165 de teste, 17 unitare, 148 de integrare.
Repo-ul livrează și un skill de agent și un plugin OpenClaw sub .agents/, publicate pe ClawHub de CI la push-uri de tag.

Limitări

Fără upload multipart, fără versionare de obiecte, fără ACL-uri dincolo de public/privat la nivel de bucket, fără creare de bucket prin API, fără headere CORS, fără replicare, fără criptare la repaus. Dacă ai nevoie de ceva din toate astea, folosește altceva.
Ia-l de aici: github.com/psyb0t/docker-hybrids3. Licențiat sub WTFPL.

Cum Îl Instalezi în Agentul Tău

Același skill, acum instalabil fără să te apropii de OpenClaw. Tot ce e sub .agents/ e catalogat într-un singur marketplace, deci sunt două comenzi:

claude plugin marketplace add psyb0t/agents
claude plugin install hybrids3@psyb0t

Codex folosește același marketplace cu alt verb, codex plugin add hybrids3@psyb0t, pentru că nu există codex plugin install. Găsește singur și skillul într-un checkout al repo-ului, pentru că scanează .agents/skills/ nativ, fără să fie instalat absolut nimic. E listat acum și pe MCP Registry-ul oficial, deci un client care rezolvă servere de acolo îl poate găsi fără să i se dea un URL.