Back to Overview

Architecture Deep Dive

Every layer explained — from the encrypted tunnel to the Bluetooth handshake to the file hitting your NVMe drive.

1

The Data Flow

How your files travel from phone to drive

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.

Everyday Usage
Your iPhone
Anywhere in the world
HTTPS / TLS
Cloudflare
TLS termination
Encrypted Tunnel
Your Pi
FastAPI :8000
Read / Write
NVMe SSD
/mnt/nvme

What happens at each hop:

1
App sends HTTPS requestEncrypted with TLS — unreadable in transit
2
Cloudflare terminates TLSValidates the domain, then forwards through the tunnel
3
Tunnel delivers to PiOutbound-only connection — no ports open on your router
4
FastAPI verifies JWTChecks signature with per-device secret (HS256)
5
Path sanitized & file servedDirectory traversal blocked, file read from NVMe SSD
6
Response encrypted & returnedSame path in reverse — end-to-end encryption

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.

2

Cloudflare Tunnel

Zero open ports, zero configuration

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.

Traditional vs UghStorage
Traditional Home Server: Internet ──▶ Port 443 OPEN ──▶ Router ──▶ Pi ↑ Exposed to attacks ↑ Requires port forwarding ↑ Needs static IP or DDNS UghStorage (Cloudflare Tunnel): Internet ──▶ Cloudflare ◀── Outbound ONLY ── Pi ↑ DDoS protected ↑ No ports open ↑ TLS handled ↑ Pi connects OUT ↑ No home IP exposed ↑ Works behind any NAT

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
3

Bluetooth Provisioning

BLE GATT server · dbus-next · BlueZ

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.

First-Time Setup
┌──────────────┐ Bluetooth ┌──────────────┐ WiFi ┌──────────┐ iPhone ──────────▶ Pi ─────────▶ Router App 1. Find BLE Server Connect 2. WiFi (no WiFi) └──────────────┘ creds └──────┬───────┘ └──────────┘ Registers with ughstorage.com Tunnel created Device is LIVE

The BLE handshake step-by-step:

1
Pi advertises "UghStorage-Setup"BLE GATT service with custom UUID via BlueZ/dbus-next
2
iPhone discovers and connectsApp scans for BLE peripherals matching the service UUID
3
App reads WiFi scan resultsPi uses nmcli (NetworkManager) to list available networks + signal strength
4
User selects network, app sends credentialsSSID + password written to BLE characteristic over Bluetooth
5
Pi connects to WiFinmcli establishes connection, Pi gets IP address from router
6
Pi registers with SupabaseCalls register-device edge function, receives device_id + tunnel_token + shared_secret
7
Cloudflare Tunnel startscloudflared connects outbound, DNS record created at *.ughstorage.com
8
App switches to cloud accessBLE disconnects, app now talks to Pi through the encrypted tunnel

BLE Characteristics (the "channels" the app and Pi communicate through):

WiFi ScanRead

Returns available WiFi networks with signal strengths

WiFi ConfigWrite

Receives SSID + password to connect Pi to WiFi

WiFi StatusRead

Connection state, SSID, and assigned IP address

User TokenWrite

App sends Supabase JWT for device registration

Server URLRead

Tunnel URL once device is registered and live

RegistrationRead

Progress 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.

4

JWT Authentication

Per-device secrets · HS256 · Supabase edge functions

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.

Token Flow
iPhone App
Needs a token
User auth
Supabase
Signs JWT with secret
Bearer token
Your Pi
Verifies signature

What's inside a JWT token:

JWT Payload
{
  "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.

5

Inside the Pi

Three systemd services · always running · auto-restart

Your Raspberry Pi 5 runs three lightweight services that start automatically on boot and restart on failure. Here's what each one does:

Pi Architecture
┌─────────────────────────────────────────────────────────────┐ Raspberry Pi 5 ┌─────────────────┐ ┌──────────────┐ ┌───────────────┐ ughstorage ughstorage cloudflared (FastAPI) -ble (tunnel) (Bluetooth) File upload Routes all Download WiFi setup traffic from Search Device ughstorage Thumbnails registration .com to Trash / Share localhost Favorites :8000 └────────┬────────┘ └──────────────┘ └───────────────┘ ┌────────┴────────┐ ┌──────────────────────────────────┐ SQLite DB /mnt/nvme (NVMe SSD) (metadata, favorites, /storage ← your files share links) /thumbnails ← auto-generated └─────────────────┘ └──────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
ughstoragePort 8000

FastAPI server — handles all file operations, search, thumbnails, trash, favorites, sharing, and device stats. Runs via Uvicorn ASGI server.

ughstorage-bleBluetooth

BLE GATT server for wireless setup. Handles WiFi provisioning and device registration — no terminal needed. Uses dbus-next + BlueZ.

cloudflaredTunnel

Cloudflare 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.

6

File Storage & Database

ext4 · SQLite WAL · aiosqlite · Pillow + ffmpeg

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.

/mnt/nvme/ ├── storage/ ← Your uploaded files │ ├── 2024/ │ ├── 2025/ │ ├── Documents/ │ └── vacation.jpg ├── thumbnails/ ← Auto-generated previews │ ├── {uuid-1}.jpg ← 300px max, JPEG quality 80 │ └── {uuid-2}.jpg └── ughstorage.db ← SQLite (metadata, favorites, share links)

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:

SQLite Schema
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'))
);
7

Security Model

5 layers · zero trust · defense in depth

UghStorage uses a defense-in-depth approach — multiple independent security layers so that a breach at one layer doesn't compromise the system.

L1
Transport Encryption
TLS/HTTPS everywhere. Cloudflare handles certificate provisioning and automatic renewal. All data encrypted in transit between your phone and your Pi.
L2
Network Isolation
Cloudflare Tunnel: zero open ports on your home network. Pi connects outbound only. Cannot be port-scanned or directly attacked. DDoS protection included.
L3
Per-Device Authentication
JWT tokens signed with a unique DEVICE_SHARED_SECRET per Pi. HS256 algorithm. Token includes device_id claim — cannot use one device's token on another.
L4
Path Traversal Protection
All file paths sanitized server-side. Requests for ../../etc/passwd are rejected. Files cannot be accessed outside the storage root directory.
L5
Physical Security
Your files physically exist on hardware in your home. No cloud copies, no third-party access. NVMe drive can be removed and encrypted if desired.
8

REST API

FastAPI · async · uvicorn · 15+ endpoints

The UghStorage server exposes a RESTful API over HTTP. All endpoints except /health and /share/{token} require JWT authentication.

MethodEndpointDescription
GET/healthServer status, version, device ID
POST/files/uploadUpload 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}/favoriteToggle favorite status
POST/files/{id}/trashSoft delete (move to trash)
POST/files/{id}/restoreRestore from trash
DELETE/files/{id}Permanent delete (from trash)
POST/files/{id}/shareCreate expiring share link
GET/share/{token}Public file access (no auth)
GET/device/statsCPU temp, RAM, disk, WiFi, uptime

Tech stack powering the API:

FastAPIv0.115

Async Python web framework with automatic OpenAPI docs

UvicornASGI

Lightning-fast ASGI server, runs on port 8000

aiosqliteAsync

Non-blocking SQLite driver — no database lock contention

PyJWTHS256

JWT token verification with per-device shared secrets

PillowImaging

Image thumbnail generation — resize, convert, cache

psutilSystem

CPU temperature, memory usage, disk stats, uptime