initial commit

This commit is contained in:
ray
2026-07-12 10:17:17 -04:00
commit dab5a4ebc6
1424 changed files with 330463 additions and 0 deletions
@@ -0,0 +1,185 @@
---
name: homarr-dashboard-setup
description: >-
Deploy, configure, and populate a Homarr dashboard — Docker setup, user
management, bulk-adding services via direct SQLite manipulation, layout
planning, and troubleshooting.
tags: [homarr, dashboard, self-hosted, homelab, sqlite, docker]
---
# Homarr Dashboard Setup
Populate a Homarr dashboard with all self-hosted services. Homarr stores everything in SQLite -- apps, items (widgets), layout positions. For bulk operations, direct database manipulation is faster than the web UI.
## Quick reference
| Path | Value |
|------|-------|
| Container | `homarr` |
| Image | `ghcr.io/homarr-labs/homarr:latest` |
| Port | `8080:7575` |
| Config dir | `./appdata` (bind mount to `/appdata`) |
| DB location | `<config>/db/db.sqlite` |
| CLI tool | `docker exec homarr homarr <command>` |
## User management
### Create first admin (no users exist)
```bash
docker exec homarr homarr recreate-admin --username ray
# Shows a generated password
```
### Set a specific password
```bash
docker exec homarr homarr users update-password \
--username ray \
--password 'your-password-here'
```
### Reset password to random (all sessions terminated)
```bash
docker exec homarr homarr reset-password --username ray
```
### List users
```bash
docker exec homarr homarr users list
```
## Database schema (key tables)
### `app` -- service definitions
```
id TEXT PRIMARY KEY -- 25-char alphanumeric ID
name TEXT -- display name
description TEXT -- optional tooltip
icon_url TEXT -- URL to icon
href TEXT -- click target URL
ping_url TEXT -- optional separate status-check URL
```
### `item` -- widgets on the board
```
id TEXT PRIMARY KEY
board_id TEXT -- FK to board.id
kind TEXT -- 'app' for app widgets, 'clock', 'weather', etc.
options TEXT -- JSON: {"json":{"appId":"<app_id>","openInNewTab":true,"showTitle":true}}
advanced_options TEXT -- usually {"json":{}}
```
### `item_layout` -- position/size on the grid
```
item_id TEXT -- FK to item.id
section_id TEXT -- FK to section.id
layout_id TEXT -- FK to layout.id
x_offset INTEGER
y_offset INTEGER
width INTEGER
height INTEGER
```
### `board` -- the dashboard page
```
id TEXT PRIMARY KEY
name TEXT -- unique name
```
### `section` -- a column/grid area on a board
```
id TEXT PRIMARY KEY
board_id TEXT -- FK to board.id
```
### `section_layout` -- which layout template a section uses
```
id TEXT PRIMARY KEY
section_id TEXT -- FK to section.id
```
## Bulk-adding services via SQLite
When adding 10+ services, direct SQLite inserts beat the UI. Steps:
### 1. Discover running services and their ports
```bash
docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}'
ss -tlnp | grep LISTEN
```
### 2. Get board/section/layout IDs
```sql
SELECT id, name FROM board;
SELECT id, board_id FROM section;
SELECT id, section_id FROM section_layout;
```
### 3. Icon URLs
Use the Homarr dashboard-icons repository:
```
https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/<name>.png
https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/svg/<name>.svg
```
Common icons: `immich.png`, `home-assistant.png`, `paperless-ngx.png`, `plex.png`,
`audiobookshelf.png`, `mealie.png`, `qbittorrent.png`, `pihole.png`,
`portainer.png`, `uptime-kuma.png`, `scrutiny.png`, `vaultwarden.png`,
`pocketbase.png`, `glitchtip.png`, `searxng.png`, `sunshine.png`
For missing icons, fall back to `chrome-beta.png` or another generic icon.
### 4. Generate IDs
Homarr uses ~25-char alphanumeric IDs. Generate them in Python:
```python
import secrets
def gen_id():
return secrets.token_urlsafe(16)[:25].replace('-','0').replace('_','1')
```
### 5. Insert apps
```sql
INSERT INTO app (id, name, icon_url, href) VALUES
('<id>', '<Name>', '<icon_url>', '<href>');
```
### 6. Insert items (one per app)
```sql
INSERT INTO item (id, board_id, kind, options, advanced_options) VALUES
('<item_id>', '<board_id>', 'app',
'{"json":{"appId":"<app_id>","openInNewTab":true,"showTitle":true}}',
'{"json":{}}');
```
### 7. Insert layout records
```sql
INSERT INTO item_layout (item_id, section_id, layout_id, x_offset, y_offset, width, height) VALUES
('<item_id>', '<section_id>', '<layout_id>', 0, 0, 1, 1);
```
### 8. Restart container
```bash
docker restart homarr
```
### 9. Verify
```bash
sqlite3 <db_path> \
"SELECT l.x_offset, l.y_offset, l.width, l.height, a.name
FROM item_layout l
JOIN item i ON l.item_id = i.id
LEFT JOIN app a ON json_extract(i.options, '$.json.appId') = a.id
ORDER BY l.y_offset, l.x_offset;"
```
## Pitfalls
- **DB owned by root**: The SQLite file is created by the Docker container and owned by `root:root`. Use `sudo` or `docker exec` for DB access.
- **Duplicate apps**: Homarr auto-discovers Docker containers and inserts into the `app` table. You may see duplicate entries -- e.g. the auto-discovered `homarr` + your manually created `Homarr`. Clean up old items/layouts if needed.
- **Layout columns are `x_offset`, `y_offset`**: Not `x`/`y`. Forgetting this causes silent misplacement.
- **IDs must be unique across `item` and `app` tables**: The random generator collisions are astronomically unlikely but verify if you suspect overlap.
- **Restart required**: Direct DB writes are not live-reloaded. Always `docker restart homarr` after inserts.
- **Container restarts reset uncommitted changes**: The DB is on a bind mount, but if your insert script hasn't committed before the container exits, changes are lost. Use explicit `connection.commit()`.
- **json_extract in joins**: When joining `item` to `app`, use `json_extract(i.options, '$.json.appId')` to extract the app reference from the item's JSON options field.
- **Nginx proxy vs direct access**: For services behind nginx on the same host, use `127.0.0.1:<port>`. For services reaching other hosts or directly exposed, use `192.168.50.98:<port>`.
@@ -0,0 +1,150 @@
# Homarr DB Schema & Reference Queries
Collected from a Homarr v16.2.10 (next-server) instance.
## Key table schemas
### app
```
CREATE TABLE IF NOT EXISTS "app" (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
description TEXT,
icon_url TEXT NOT NULL,
href TEXT,
ping_url TEXT
);
```
### item
```
CREATE TABLE IF NOT EXISTS "item" (
id TEXT PRIMARY KEY NOT NULL,
board_id TEXT NOT NULL,
kind TEXT NOT NULL,
options TEXT DEFAULT '{"json": {}}' NOT NULL,
advanced_options TEXT DEFAULT '{"json": {}}' NOT NULL,
FOREIGN KEY (board_id) REFERENCES board(id) ON UPDATE no action ON DELETE cascade
);
```
### item_layout
```
CREATE TABLE IF NOT EXISTS "item_layout" (
item_id TEXT NOT NULL,
section_id TEXT NOT NULL,
layout_id TEXT NOT NULL,
x_offset INTEGER NOT NULL,
y_offset INTEGER NOT NULL,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
PRIMARY KEY(item_id, section_id, layout_id),
FOREIGN KEY (item_id) REFERENCES item(id) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (section_id) REFERENCES section(id) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (layout_id) REFERENCES layout(id) ON UPDATE no action ON DELETE cascade
);
```
### board
```
CREATE TABLE IF NOT EXISTS "board" (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL UNIQUE,
is_public INTEGER DEFAULT false NOT NULL,
creator_id TEXT,
page_title TEXT,
meta_title TEXT,
logo_image_url TEXT,
favicon_image_url TEXT,
background_image_url TEXT,
background_image_attachment TEXT DEFAULT 'fixed' NOT NULL,
background_image_repeat TEXT DEFAULT 'no-repeat' NOT NULL,
background_image_size TEXT DEFAULT 'cover' NOT NULL,
primary_color TEXT DEFAULT '#fa5252' NOT NULL,
secondary_color TEXT DEFAULT '#fd7e14' NOT NULL,
opacity INTEGER DEFAULT 100 NOT NULL,
custom_css TEXT,
disable_status INTEGER DEFAULT false NOT NULL,
item_radius TEXT DEFAULT 'lg' NOT NULL,
icon_color TEXT,
FOREIGN KEY (creator_id) REFERENCES user(id) ON UPDATE no action ON DELETE set null
);
```
### section
```
CREATE TABLE IF NOT EXISTS "section" (
id TEXT PRIMARY KEY NOT NULL,
board_id TEXT NOT NULL,
kind TEXT DEFAULT 'empty' NOT NULL,
x_offset INTEGER DEFAULT 0 NOT NULL,
y_offset INTEGER DEFAULT 0 NOT NULL,
min_width INTEGER,
min_height INTEGER,
FOREIGN KEY (board_id) REFERENCES board(id) ON UPDATE no action ON DELETE cascade
);
```
### section_layout
```
CREATE TABLE IF NOT EXISTS "section_layout" (
id TEXT PRIMARY KEY NOT NULL,
section_id TEXT NOT NULL,
layout_id TEXT NOT NULL,
FOREIGN KEY (section_id) REFERENCES section(id) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (layout_id) REFERENCES layout(id) ON UPDATE no action ON DELETE cascade
);
```
### user
```
CREATE TABLE IF NOT EXISTS "user" (
id TEXT PRIMARY KEY NOT NULL,
name TEXT,
email TEXT,
email_verified INTEGER,
image TEXT,
password TEXT,
provider TEXT DEFAULT 'credentials' NOT NULL,
home_board_id TEXT,
mobile_home_board_id TEXT,
...
FOREIGN KEY (home_board_id) REFERENCES board(id) ON UPDATE no action ON DELETE set null
);
```
## Useful queries
### List all apps
```sql
SELECT id, name, href, icon_url FROM app ORDER BY name;
```
### List all items on a board with their app names
```sql
SELECT i.id, i.kind, a.name
FROM item i
LEFT JOIN app a ON json_extract(i.options, '$.json.appId') = a.id
WHERE i.board_id = '<board_id>'
ORDER BY a.name;
```
### Full layout grid with service names
```sql
SELECT l.x_offset, l.y_offset, l.width, l.height, a.name
FROM item_layout l
JOIN item i ON l.item_id = i.id
LEFT JOIN app a ON json_extract(i.options, '$.json.appId') = a.id
ORDER BY l.y_offset, l.x_offset;
```
### Delete all items for a board (clean slate)
```sql
DELETE FROM item_layout WHERE item_id IN (SELECT id FROM item WHERE board_id = '<board_id>');
DELETE FROM item WHERE board_id = '<board_id>';
```
### Count items per kind
```sql
SELECT kind, COUNT(*) FROM item GROUP BY kind;
```