Vue 3 + Vite web interface for an NVIDIA DGX Spark. Runs as a Docker container with nginx, on the box itself.
Six tabs, one responsive codebase — the phone and the desktop render the same components.
- Chat — streaming chat with local Ollama models, with three things folded in:
- ⚛ Quantum steering — a flag, not a separate mode: each turn is preceded by a live measurement from an attached QCicada hardware RNG, shown inline
- Saved conversations — server-side, restored with their model and settings
- Image input — on the models that report a
visioncapability
- Agent — Claude Code on the box, with a persona picker. Can edit this repo.
- Goose — research agent with web search
- Image Generation — SDXL (Pony, NoobAI, Illustrious) and Flux via ComfyUI
- Manage — Docker/systemd control, live system stats, and a torrent client
- Overview — hardware, live model list, service links
- Installable to an iOS home screen as a PWA
| Service | Port | Needed for |
|---|---|---|
| Ollama | 11434 | Chat, model list |
| ComfyUI | 8188 | Image generation |
| Telemetry API | 8006 | Live stats |
| QCicada QRNG | 8007 | Quantum steering (optional) |
| Torrent API + aria2 | 8009, 6800 | Torrents (optional) |
Both containers run with network_mode: host and reach every service over the
host's own loopback. This is deliberate: Ollama binds 127.0.0.1 only, so a bridged
container cannot reach it at all.
Do not set OLLAMA_ORIGINS=*. Ollama and ComfyUI reject non-localhost origins
with 403, and nginx strips the Origin header on those two routes instead — which
vouches only for requests arriving through this proxy, rather than opening both
services to every origin from every client on the box.
git clone https://github.com/vybe/sparky
cd sparkyCopy the example files:
cp .env.example .env
chmod +x deploy.shEdit .env with your server details:
DGX_HOST=your-server-ip # Your DGX/server IP or hostname
DGX_USER=your-username # SSH username
DGX_PASS=your-password # SSH password (or use SSH keys)
REMOTE_DIR=/home/$USER/dgx-web-ui # Optional: custom deployment pathRequired. See Access control below for why this is not optional.
SPARKY_AUTH_USER=your-username
SPARKY_AUTH_PASS=use-a-long-random-valueService links need no configuration — they are built from
window.location.hostname, so the app works over the LAN, over a VPN, or through a
tunnel unchanged. Hardware specs live in src/components/Dashboard.vue; the model
list is read live from Ollama.
Ensure these services are accessible on your server:
| Service | Default Port | How to Check |
|---|---|---|
| Ollama | 11434 | curl http://localhost:11434/api/version |
| ComfyUI | 8188 | curl http://localhost:8188/system_stats |
| Telemetry | 8006 | curl http://localhost:8006/stats |
| QCicada QRNG | 8007 | curl http://localhost:8007/health |
Sparky reaches all of these over the host's loopback, so none of them needs to be exposed on Sparky's account.
On the reference deployment several are nonetheless bound to 0.0.0.0 — ComfyUI
(8188), the telemetry API (8006) and the QRNG service (8007) — because they are also
used directly, and the Overview tab links to them. That is a deliberate choice on a
VPN-restricted box, not an accident, but it does mean they are reachable from the
LAN without Sparky's Basic auth. Only the QRNG service has authentication of its
own. Bind them to 127.0.0.1 if you do not want that.
The backend (3081) is the exception and is loopback-only by design: it exposes container control and shell exec.
If your ports differ, update config.dgx.js and nginx.conf accordingly.
./deploy.sh deployThe web UI will be available at:
- Web UI:
http://your-server-ip:3080 - API:
http://your-server-ip:3081/docs
Nothing to switch on — the layout adapts below 768px. ?tab=<id> deep-links a tab
and ?agent=<id> also picks an agent persona.
The UI requires HTTP Basic auth (see Access control), so on a phone either enter
the credentials when prompted or bookmark http://user:pass@host:3080/ — both work.
On iOS, use Safari → Share → Add to Home Screen for a standalone app experience.
Before deploying, ensure you've configured:
-
.envhasDGX_HOST/DGX_USERandSPARKY_AUTH_USER/SPARKY_AUTH_PASS -
deploy.shexecutable - Hardware specs in
src/components/Dashboard.vuematch your box - Service ports reachable on the host's loopback
- Only port 3080 reachable from the network
📖 See CONFIGURATION.md for the complete configuration guide covering:
- Network IPs and VPN setup
- Hardware specifications display
- Model lists (LLM, image, video, audio)
- Service ports and links
- Backend API customization
- Docker Compose settings
For advanced customization, see SETUP.md
For local development with API tunneling:
# Install dependencies
npm install
# Run dev server
npm run dev
# Open http://localhost:3000The development server expects services to be accessible on localhost ports (typically via SSH tunneling).
Runtime config is loaded from window.DGX_CONFIG (defined in public/config.js or config.dgx.js).
Local development (public/config.js):
window.DGX_CONFIG = {
COMFYUI_URL: '/comfyui',
OLLAMA_URL: '/ollama',
QRNG_URL: '/qrng',
API_URL: '/api',
TELEMETRY_URL: '/telemetry',
APP_NAME: 'Sparky',
VERSION: '2.0.0'
};Production (config.dgx.js): the same same-origin paths, proxied by nginx.
src/config.js resolves each one to an absolute URL against location.origin.
That is deliberate: a relative fetch resolves against the document URL, and
Chrome refuses to construct a Request from a URL carrying credentials — so
http://user:pass@host/ would otherwise break every request in the app.
- Vue 3 with Composition API
- Vite 7 for build tooling
- Tailwind CSS 4 for styling
- PWA support (manifest + service worker)
- FastAPI (Python)
- Docker container management
- System monitoring
- Claude Code integration
- Multi-stage Docker build
- Nginx for static file serving
- Docker Compose for orchestration
./deploy.sh sync # Sync files only
./deploy.sh build # Rebuild Docker image
./deploy.sh start # Start containers
./deploy.sh stop # Stop containers
./deploy.sh restart # Restart containers
./deploy.sh logs # View container logs
./deploy.sh status # Check container status
./deploy.sh deploy # Full deployment (sync + build + start)| Service | Default Port | Purpose |
|---|---|---|
| Web UI | 3080 | nginx — the only port that should be reachable from the network |
| API | 3081 | Backend; binds 127.0.0.1 only (container control + shell exec) |
| ComfyUI | 8188 | Image generation |
| Ollama | 11434 | LLM inference |
| QCicada QRNG | 8007 | Quantum steering |
| Telemetry | 8006 | System stats |
| Document | Purpose |
|---|---|
| GETTING_STARTED.md | Overview and preparation checklist |
| CONFIGURATION.md | Complete configuration guide |
| SETUP.md | Advanced setup and customization |
| SECURITY.md | Security best practices and policies |
| CONTRIBUTING.md | How to contribute to this project |
Expected without credentials. If it happens with them, check SPARKY_AUTH_USER /
SPARKY_AUTH_PASS in .env and redeploy — the credential is hashed at deploy time,
so editing .env alone changes nothing.
nginx is not stripping the Origin header on /ollama/. Ollama rejects any
non-localhost origin; curl succeeds (no Origin) while the browser fails. Fix the
proxy — do not set OLLAMA_ORIGINS=*, which opens Ollama to every origin from
every client on the box.
Opened as http://user:pass@host/ against a build older than 2026-08-02. Relative
fetches threw because the document URL carried credentials. Redeploy.
Check ComfyUI is running:
sudo docker ps | grep comfyui
sudo docker logs comfyui --tail 50Restart if needed:
sudo docker restart comfyuiThe Voice tab was removed on 2026-08-02 — the ultravox-vllm container has been
Exited(137) since early 2026. Chatterbox TTS still runs on :8004 if it is ever
revived. Historical note:
sudo docker ps | grep ultravox
sudo docker restart ultravox-vllmBackend API must be running:
sudo docker ps | grep dgx-api
sudo docker logs dgx-api --tail 50The UI requires HTTP Basic auth, and this is not optional. The Agent tab runs
Claude Code as your user with --dangerously-skip-permissions, and nginx binds
0.0.0.0 — without a credential the app is an unauthenticated shell for anyone who
can reach the port. A VPN ACL does not cover the LAN.
Set it in .env:
SPARKY_AUTH_USER=your-username
SPARKY_AUTH_PASS=use-a-long-random-valuedeploy.sh hashes it into an nginx {SHA} htpasswd line before it leaves your
machine, so the plaintext never reaches the server or the repo. With no credential
configured the app fails closed and denies everything.
/health is exempt so the container healthcheck still works.
- The backend binds
127.0.0.1:3081. It exposes container control and shell exec, so it must never be published — on host networking0.0.0.0would put it on the LAN and the VPN. - The backend needs the Docker socket for container management.
- Claude Code runs with
--dangerously-skip-permissions. - Never commit credentials or internal IPs.
.secret-patterns(gitignored, see.secret-patterns.example) feeds the scanners in.claude/; putting the literal secret in the checklist that searches for it is how it leaked the first time. - Plain HTTP is acceptable inside a WireGuard/Tailscale tunnel; it is not on an open network. Basic auth over plain HTTP sends the credential in every request.
- Vue 3 + Composition API
- Vite 7
- Tailwind CSS 4
- Docker + nginx (production)
- PWA (manifest + service worker)
- FastAPI (backend)
MIT
Pull requests welcome! Please ensure:
- Code follows existing style
- Documentation is updated
- No hardcoded credentials or environment-specific values
See CONTRIBUTING.md for details.
Built with Claude Code