2b7290626ec487b5c7b38e304b232076d57e8be4
The deferred 'hardlink blobs from tarball' optimization from DESIGN.md
landed as 'just walk the tarball and write blobs separately' for v1.
GET /v1/blob/{sha} was 404'ing because the blob store was empty —
storage only had snapshots/<id>.tar.zst and a manifest.
Server now:
1. Parses uploaded multipart manifest + tarball
2. Walks the tar entries, computes each entry's sha256
3. Cross-checks against the manifest's declared sha (rejects 400 on mismatch)
4. Writes each blob to <user>/blobs/ via Storage.WriteBlob
5. Then stores the snapshot tarball + manifest as before
2 new tests cover: (a) POST then GET /v1/blob/{sha} round-trip,
(b) manifest-claims-different-sha-than-tarball rejection.
Discovered via e2e smoke against frazclient: pull 404'd on every blob
after a successful push. 33/33 tests pass.
cloud-svc
Steam-Cloud-style per-user file sync for Minecraft clients. Pull on launch, push on exit, with per-file mtime conflict resolution + N-snapshot history.
Part of the automc platform. See DESIGN.md for architecture.
Status
Skeleton. Code, tests, build all working. Not yet deployed.
- ✅ HTTP API (7 endpoints)
- ✅ On-disk blob + tarball + manifest storage
- ✅ Token verification via auth-service (with 60s cache)
- ✅ Dev mode (accept any bearer)
- ✅ Quota enforcement
- ⏳ Pg-backed per-user quota override (currently DefaultQuota only)
- ⏳ Snapshot retention / GC of unreferenced blobs
- ⏳ Tarball-vs-manifest content cross-check on push
Build + test
make build
make test
make vet
31 tests, 0 fails.
Run locally (dev mode)
make dev
Listens on 127.0.0.1:9091. Accepts ANY non-empty bearer token; the token becomes the user ID. Files land under ./data/.
Run against real auth-service
CLOUD_LISTEN=127.0.0.1:9091 \
CLOUD_STORAGE_ROOT=/var/lib/cloud-svc/data \
CLOUD_AUTH_SERVICE_URL=http://auth-service:9090 \
CLOUD_SERVICE_KEY=$(cat /etc/cloud-svc/service-key) \
CLOUD_DEFAULT_QUOTA_MB=200 \
./cloud-svc
API quick reference
| Method | Path | Purpose |
|---|---|---|
GET |
/v1/manifest |
Latest manifest for caller |
GET |
/v1/blob/{sha256} |
Raw file content |
POST |
/v1/snapshot |
Multipart upload: manifest + tarball |
GET |
/v1/snapshots |
List caller's snapshot history |
GET |
/v1/snapshot/{id} |
Historical tarball |
DELETE |
/v1/snapshot/{id} |
Remove (latest protected) |
GET |
/v1/quota |
{used_bytes, limit_bytes, snapshots} |
GET |
/healthz |
Unauthenticated liveness probe |
All authenticated endpoints require Authorization: Bearer <token> with cloud:rw scope.
See DESIGN.md for full details + on-disk layout + conflict semantics.
Configuration (env)
| Var | Default | Purpose |
|---|---|---|
CLOUD_LISTEN |
127.0.0.1:9091 |
HTTP bind address |
CLOUD_STORAGE_ROOT |
/data |
Root dir for blobs + snapshots |
CLOUD_AUTH_SERVICE_URL |
http://auth-service:9090 |
auth-service base URL |
CLOUD_SERVICE_KEY |
(required when not dev) | cloud-svc's own X-API-Key for calling auth-service |
CLOUD_AUTH_CACHE_TTL |
60s |
Verified-token cache TTL |
CLOUD_DEFAULT_QUOTA_MB |
200 |
Per-user quota (MB) |
CLOUD_DEV_MODE |
unset | If 1: accept any bearer; bypass auth-service |
Description
Steam-Cloud-style per-user state sync for Minecraft clients. Part of the automc platform. Pulls/pushes per-Discord-user file state with per-file mtime conflict resolution + N-snapshot history.
Languages
Go
98.7%
Dockerfile
0.7%
Makefile
0.6%