A self-hosted iOS springboard-style dashboard for your home services. Organize apps, bookmarks and widgets on a paginated grid with drag-and-drop, search and full JSON persistence.
- Paginated grid - swipe or arrow-key between pages, free-placement coordinate layout.
- Drag & drop - reorder apps across pages with live drop preview.
- Search - full-screen instant search overlay.
- App management - add, edit, delete apps from the web UI. Drop an external URL onto the grid to auto-create an app with its favicon.
- Folders - group apps into folders via a modal overlay.
- Widgets - load custom widgets from the
gallery/directory. Supports JSON and TSX widget formats, rendered in shadow DOM. - Uploads - custom icons and wallpapers uploaded through the settings panel.
- Themes - glassmorphism UI, icon shape selection (square, rounded, circle), adjustable grid spacing.
- Weather chip - optional weather display with city-based lookup (toggleable).
- Localization - persistent EN / FR switch.
- PWA - installable on iPhone / Android home screen with proper icons and manifest.
- Backup - full export / import with base64-encoded assets and automatic daily snapshots (retention: 10).
- Dashboard branding - customizable dashboard name, page title, and favicon.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite 5 |
| Backend | Express 4, Node.js 20+ |
| Persistence | JSON file (data/dashboard.json) |
| Uploads | Multer → public/uploads/ |
| Widgets | Shadow DOM runtime, TSX/JSON |
home_dashboard/
├── server/
│ ├── index.js # Express API + static file serving
│ └── storage.js # JSON persistence, backup, gallery management
├── src/
│ ├── App.jsx # Main dashboard state & rendering
│ ├── i18n.js # EN/FR translations
│ ├── lib/
│ │ └── api.js # Client-side API wrappers
│ └── components/
│ ├── AppTile.jsx
│ ├── FolderOverlay.jsx
│ ├── FolderTile.jsx
│ ├── SearchOverlay.jsx
│ ├── SettingsModal.jsx
│ └── ShadowWidget.jsx
├── gallery/ # Widget source files (JSON, TSX)
├── public/
│ ├── icons/ # PWA icons
│ ├── uploads/ # User-uploaded icons & wallpapers
│ ├── manifest.webmanifest
│ └── sw.js # Service worker (app shell only)
├── data/
│ └── dashboard.json # Persisted dashboard state
├── backups/ # Automatic daily snapshots
├── Dockerfile
├── docker-compose.yml
├── vite.config.js
├── package.json
└── index.html
No need to clone the repository. Just create a docker-compose.yml anywhere on your machine:
services:
limb:
image: ghcr.io/infinition/limb:latest
container_name: limb
restart: unless-stopped
ports:
- "8090:3001"
volumes:
- ./data:/app/data
- ./backups:/app/backups
- ./gallery:/app/gallery
- ./uploads:/app/public/uploads
environment:
- NODE_ENV=production
- PORT=3001Then run:
docker compose up -dThat's it. The dashboard is accessible at http://<host>:8090.
To update to the latest version:
docker compose pull && docker compose up -dOptional: automatic updates with Watchtower
Add this service to your docker-compose.yml to automatically pull new versions every 5 minutes:
watchtower:
image: containrrr/watchtower
container_name: watchtower-limb
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- WATCHTOWER_CLEANUP=true
- WATCHTOWER_POLL_INTERVAL=300
command: limbNAS / custom volume paths
If your data directory is elsewhere (e.g. Synology NAS), adjust the volume paths:
volumes:
- /volume1/docker/limb/data:/app/data
- /volume1/docker/limb/backups:/app/backups
- /volume1/docker/limb/gallery:/app/gallery
- /volume1/docker/limb/uploads:/app/public/uploadsFor contributors who want to work on the code:
git clone https://github.com/infinition/limb.git
cd limb
npm install
npm run devThis starts both the Express API on http://localhost:3001 and the Vite dev server on http://localhost:5173.
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
Server listening port |
NODE_ENV |
- | Set to production for optimized serving |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/dashboard |
Fetch dashboard state + widget library + assets |
PUT |
/api/dashboard |
Save dashboard state |
POST |
/api/upload/icon |
Upload an icon |
POST |
/api/upload/wallpaper |
Upload a wallpaper |
DELETE |
/api/upload/icon/:name |
Delete an icon |
DELETE |
/api/upload/wallpaper/:name |
Delete a wallpaper |
GET |
/api/widgets/gallery |
List available widgets |
POST |
/api/widgets/gallery |
Save a widget to the gallery |
POST |
/api/widgets/import |
Import a widget file |
DELETE |
/api/widgets/gallery/:file |
Delete a widget |
PUT |
/api/widgets/:id/state |
Save widget instance state |
GET |
/api/backup/export |
Download full backup bundle |
POST |
/api/backup/import |
Restore from a backup bundle |
Place a .json or .tsx file in the gallery/ directory.
JSON format:
{
"name": "My Widget",
"description": "A simple counter",
"defaultW": 2,
"defaultH": 2,
"html": "<div id=\"root\">0</div>",
"css": "#root { font-size: 2rem; text-align: center; }",
"js": "let c=0; document.getElementById('root').onclick=()=>{document.getElementById('root').textContent=++c;}"
}Widgets can also be imported directly from the Settings panel.
MIT