The Data Flow
When you open the UghStorage app and tap on a file, here's the exact path your request takes — and why your data never touches a third-party server.
What happens at each hop:
Cloudflare never stores your files. It only passes encrypted traffic between your phone and your Pi. Your data physically lives on the NVMe drive at home — nowhere else.
Cloudflare Tunnel
The biggest challenge with home servers is making them accessible from outside your network. Traditional approaches require port forwarding, dynamic DNS, and firewall rules. UghStorage uses Cloudflare Tunnel instead — a fundamentally different approach.
How the tunnel is provisioned:
During device registration, the Pi receives a tunnel_token from the Supabase edge function. The cloudflared daemon uses this token to establish a persistent outbound connection to Cloudflare's edge network. Cloudflare then creates a DNS record: {device-uuid}.ughstorage.com → tunnel endpoint.
Key benefits:
- Zero port forwarding — nothing exposed on your home network
- No static IP required — works even if your ISP changes your IP daily
- DDoS protection — Cloudflare absorbs attacks before they reach you
- Auto-reconnect — tunnel re-establishes after network changes or power outages
- Free TLS certificates — Cloudflare handles HTTPS and certificate renewal
Bluetooth Provisioning
When the Pi first boots, it has no WiFi and no internet. So how does the app configure it? Bluetooth Low Energy (BLE). The Pi runs a BLE GATT server that the iPhone app communicates with — no network required.
The BLE handshake step-by-step:
nmcli (NetworkManager) to list available networks + signal strengthnmcli establishes connection, Pi gets IP address from routerregister-device edge function, receives device_id + tunnel_token + shared_secretcloudflared connects outbound, DNS record created at *.ughstorage.comBLE Characteristics (the "channels" the app and Pi communicate through):
WiFi ScanReadReturns available WiFi networks with signal strengths
WiFi ConfigWriteReceives SSID + password to connect Pi to WiFi
WiFi StatusReadConnection state, SSID, and assigned IP address
User TokenWriteApp sends Supabase JWT for device registration
Server URLReadTunnel URL once device is registered and live
RegistrationReadProgress updates during the registration process
Moving to a new house? Just open the app near the Pi — it reconnects over Bluetooth, you pick the new WiFi, and the tunnel auto-reconnects. Same subdomain, no re-registration needed.
JWT Authentication
Every request to your Pi carries a JSON Web Token (JWT) signed with a secret that only your device knows. Even if someone intercepts the encrypted traffic, they can't forge a valid token without the DEVICE_SHARED_SECRET.
What's inside a JWT token:
{
"sub": "user-uuid-from-supabase",
"device_id": "your-pi-device-uuid",
"iat": 1711929600,
"exp": 1711933200
}
The Pi's require_auth() middleware validates every incoming request: checks the signature using DEVICE_SHARED_SECRET, confirms the token hasn't expired, and verifies the device_id matches this Pi. A token from one device cannot be used on another — each Pi has its own unique secret.
Inside the Pi
Your Raspberry Pi 5 runs three lightweight services that start automatically on boot and restart on failure. Here's what each one does:
ughstoragePort 8000FastAPI server — handles all file operations, search, thumbnails, trash, favorites, sharing, and device stats. Runs via Uvicorn ASGI server.
ughstorage-bleBluetoothBLE GATT server for wireless setup. Handles WiFi provisioning and device registration — no terminal needed. Uses dbus-next + BlueZ.
cloudflaredTunnelCloudflare Tunnel client. Routes traffic from ughstorage.com to localhost:8000. Zero config after initial setup.
All three services are managed by systemd — they auto-start on boot and auto-restart on failure. After a power outage, everything comes back within ~60 seconds with zero manual intervention.
File Storage & Database
Files live on your NVMe SSD formatted with ext4 — a battle-tested Linux filesystem. Metadata is tracked in a SQLite database using Write-Ahead Logging (WAL) for concurrent access.
Thumbnail generation:
- Images: Generated with Pillow (PIL). Max 300px dimension, JPEG quality 80. RGBA/P converted to RGB.
- Videos: First frame extracted with ffmpeg, scaled to 300px, saved as JPEG.
- Caching: Thumbnails generated on first request and cached on disk. Only regenerated if the source file changes.
Database schema — the files table tracks every uploaded file:
CREATE TABLE files (
id TEXT PRIMARY KEY, -- UUID
filename TEXT NOT NULL, -- "photo.jpg"
path TEXT, -- "2024/vacations/"
size INTEGER NOT NULL, -- bytes
mime_type TEXT, -- "image/jpeg"
checksum TEXT NOT NULL, -- SHA256 integrity
is_favorite BOOLEAN DEFAULT 0,
is_trashed BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT (datetime('now'))
);
Security Model
UghStorage uses a defense-in-depth approach — multiple independent security layers so that a breach at one layer doesn't compromise the system.
DEVICE_SHARED_SECRET per Pi. HS256 algorithm. Token includes device_id claim — cannot use one device's token on another.../../etc/passwd are rejected. Files cannot be accessed outside the storage root directory.REST API
The UghStorage server exposes a RESTful API over HTTP. All endpoints except /health and /share/{token} require JWT authentication.
| Method | Endpoint | Description |
|---|---|---|
| GET | /health | Server status, version, device ID |
| POST | /files/upload | Upload file with SHA256 checksum |
| GET | /files/{id} | Download file by ID |
| GET | /files/thumbnail/{id} | Get cached thumbnail (auto-generated) |
| GET | /files/search?q=&type= | Full-text search with type filtering |
| POST | /files/{id}/favorite | Toggle favorite status |
| POST | /files/{id}/trash | Soft delete (move to trash) |
| POST | /files/{id}/restore | Restore from trash |
| DELETE | /files/{id} | Permanent delete (from trash) |
| POST | /files/{id}/share | Create expiring share link |
| GET | /share/{token} | Public file access (no auth) |
| GET | /device/stats | CPU temp, RAM, disk, WiFi, uptime |
Tech stack powering the API:
FastAPIv0.115Async Python web framework with automatic OpenAPI docs
UvicornASGILightning-fast ASGI server, runs on port 8000
aiosqliteAsyncNon-blocking SQLite driver — no database lock contention
PyJWTHS256JWT token verification with per-device shared secrets
PillowImagingImage thumbnail generation — resize, convert, cache
psutilSystemCPU temperature, memory usage, disk stats, uptime