All work

GPUShare

My desktop GPU sits idle most of the day. I built a way for friends to share it for local AI and rendering, with the limits of a home machine visible.

I built the React app and FastAPI services, connected them to the GPU, and wrote the accounting and setup scripts.

Two layers: an always-on cloud layer holding accounts, ledger and invoices, and a home GPU host running Ollama and Blender that connects outbound through a tunnel with no open ports.

Idle GPU, billed at cost

Two services, one trust boundary

Always on
Accounts · ledger · invoices Reachable with your PC off
Outbound tunnel
No open ports The host connects out; nothing connects in
Your machine
Ollama · Blender Only while powered on
kWh rate × GPU watts = price. No markup.

A GPU that sits idle most of the day

My desktop GPU sits idle most of the day, so I built GPUShare for a few people I trust to use it for local AI and rendering. When the machine is off, the interface says so.

More than a model proxy

There are accounts and invites, model selection, chat, rendering and MCP settings in the React app. Public routes use JWTs and bcrypt-hashed API keys, and a raw API key is only shown when it’s created. I also built the account and admin views and the initial admin setup.

Two services, one trust boundary

The Vercel frontend reaches public middleware through a Cloudflare Tunnel. That middleware owns authentication, billing, database-backed account operations, aggregation, and short-lived in-process caches. It forwards hardware work with a shared internal secret to a backend that runs Ollama, Blender, GPU health checks, MCP tools, and file operations. The tunnel targets middleware rather than the backend, although Docker Compose still publishes the backend port on the host.

Local and cloud are different paths

Chat requests can stream back through the OpenAI-compatible endpoints, with structured tool calls handled for Ollama and OpenRouter. Ollama runs on the host through a process-local GPU queue. OpenRouter is an optional route for selected cloud models or the configurable auto model. A failed local request won’t switch to cloud by itself, and a selected cloud request sends the prompt to that provider.

Exact ledger, estimated energy

Credits and charges are append-only rows whose balance is calculated by summing the ledger. Local inference cost uses the configured electricity tariff, detected or configured GPU and system wattage, estimated token counts, and a default throughput assumption. Render cost uses elapsed job time and configured wattage. OpenRouter usage uses provider pricing. These are allocation estimates, not per-request wall-power measurements.

Rendering assumes trusted files

A render submission validates the .blend extension and size, removes Blender text blocks, uploads the sanitised file to optional R2 storage, and queues a separate worker to render and package the output. Script stripping reduces risk but is not a complete sandbox, so the current design should not accept adversarial uploads.

The installer is currently broken

The macOS, Linux, and Windows scripts detect hardware, recommend model sizes, estimate wattage, generate secrets, configure optional services, and run health checks. After the backend/middleware split, their migration and troubleshooting commands still reference the removed fastapi Compose service. That must be corrected before the installer can again be described as an end-to-end one-click path.

Outcome

The app has accounts, local and optional cloud inference, MCP tools, queued rendering and append-only usage accounting. It still depends on the host being available, and the installer has a migration command pointing at a service I removed when I split the backend.

From this project

All work