248 lines
6.2 KiB
Markdown
248 lines
6.2 KiB
Markdown
# syco.me — Homelab Dashboard
|
|
|
|
A personal homelab dashboard built with React + TypeScript on the frontend and an Express proxy server on the backend. Deployed via Woodpecker CI to a Proxmox LXC container.
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
- **Dashboard** — live widgets for all homelab services
|
|
- **Apps** — icon grid of all self-hosted apps with local icons (no CDN)
|
|
- **Home** — mobile-optimised page with weather and public transit departures
|
|
- **Responsive** — desktop defaults to Dashboard, mobile defaults to Home
|
|
- **Collapsible sections** — click any section label to fold it away
|
|
- **Header** — live clock, weather summary, and navigation tabs
|
|
|
|
---
|
|
|
|
## Stack
|
|
|
|
| Layer | Technology |
|
|
|---|---|
|
|
| Frontend | React 18, TypeScript, Vite |
|
|
| Backend | Node.js, Express |
|
|
| Routing | React Router v6 |
|
|
| Fonts | Syne, JetBrains Mono (Google Fonts) |
|
|
| CI/CD | Woodpecker CI |
|
|
| Deployment | Docker on Proxmox LXC |
|
|
|
|
---
|
|
|
|
## Widgets
|
|
|
|
### Infrastructure
|
|
| Widget | Service | API |
|
|
|---|---|---|
|
|
| Proxmox | Proxmox VE | REST API (API Token) |
|
|
| Synology NAS | DSM | Synology Web API |
|
|
| AdGuard Home | AdGuard | REST API (Basic Auth) |
|
|
| Headscale | Headscale VPN | REST API (API Key) |
|
|
| FritzBox | AVM FRITZ!Box | TR-064 SOAP API |
|
|
|
|
### Media
|
|
| Widget | Service | API |
|
|
|---|---|---|
|
|
| Jellyfin | Jellyfin | REST API (API Key) |
|
|
| Navidrome | Navidrome | Subsonic API |
|
|
| RomM | RomM | REST API |
|
|
| Arr Calendar | Sonarr + Radarr | REST API |
|
|
| Arr Stats | Sonarr + Radarr + Lidarr | REST API |
|
|
| qBittorrent | qBittorrent | Web API |
|
|
|
|
### Monitoring
|
|
| Widget | Service | API |
|
|
|---|---|---|
|
|
| Uptime Kuma | Kuma | REST API |
|
|
| CrowdSec | CrowdSec LAPI | REST API (Bouncer Key) |
|
|
| Grafana | Grafana | REST API |
|
|
| Prometheus | Prometheus | REST API |
|
|
| Loki | Loki | REST API |
|
|
|
|
### Access
|
|
| Widget | Service | API |
|
|
|---|---|---|
|
|
| Authentik | Authentik | REST API (API Token) |
|
|
| Vaultwarden | Vaultwarden | Admin API |
|
|
|
|
### Home (mobile)
|
|
| Widget | Source |
|
|
|---|---|
|
|
| Weather | [wttr.in](https://wttr.in) — no API key needed |
|
|
| Transit | [VAG PULS API](https://start.vag.de/dm/api/) — no API key needed |
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
├── server/
|
|
│ ├── index.ts # Express server + route registration
|
|
│ └── routes/ # One file per service proxy
|
|
├── src/
|
|
│ ├── components/
|
|
│ │ ├── Header.tsx # Nav, clock, weather summary
|
|
│ │ ├── AppsSection.tsx # Icon tile grid
|
|
│ │ └── widgets/ # One component per dashboard widget
|
|
│ ├── config/
|
|
│ │ ├── apps.ts # App links + icon paths (edit order here)
|
|
│ │ └── dashboard.ts # Dashboard sections + widget order (edit order here)
|
|
│ ├── hooks/
|
|
│ │ └── useIsMobile.ts
|
|
│ ├── pages/
|
|
│ │ ├── AppsPage.tsx
|
|
│ │ └── MobileHome.tsx # Weather + transit for mobile
|
|
│ └── App.tsx # Routing
|
|
├── public/
|
|
│ └── icons/ # All icons stored locally
|
|
├── Dockerfile
|
|
└── .woodpecker.yml
|
|
```
|
|
|
|
> **To change widget/app order**: edit `src/config/dashboard.ts` (widgets) or `src/config/apps.ts` (app tiles) — no need to touch `App.tsx`.
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
Copy `.env.example` to `.env` and fill in your values.
|
|
|
|
```env
|
|
# Proxmox VE
|
|
PROXMOX_HOST=https://192.168.178.x:8006
|
|
PROXMOX_TOKEN=PVEAPIToken=user@realm!tokenid=...
|
|
|
|
# Synology DSM
|
|
SYNOLOGY_HOST=http://192.168.178.x:5000
|
|
SYNOLOGY_USER=...
|
|
SYNOLOGY_PASSWORD=...
|
|
|
|
# AdGuard Home
|
|
ADGUARD_HOST=http://192.168.178.x
|
|
ADGUARD_USER=...
|
|
ADGUARD_PASSWORD=...
|
|
|
|
# CrowdSec
|
|
CROWDSEC_HOST=http://192.168.178.x:8080
|
|
CROWDSEC_API_KEY=...
|
|
|
|
# Headscale
|
|
HEADSCALE_HOST=http://192.168.178.x:8085
|
|
HEADSCALE_API_KEY=...
|
|
|
|
# FRITZ!Box
|
|
FRITZBOX_HOST=http://192.168.178.1
|
|
|
|
# Uptime Kuma
|
|
KUMA_HOST=http://192.168.178.x:3001
|
|
KUMA_USER=...
|
|
KUMA_PASSWORD=...
|
|
|
|
# Authentik
|
|
AUTHENTIK_HOST=http://192.168.178.x:9000
|
|
AUTHENTIK_TOKEN=...
|
|
|
|
# Vaultwarden
|
|
VAULTWARDEN_HOST=http://192.168.178.x:8087
|
|
VAULTWARDEN_ADMIN_TOKEN=...
|
|
|
|
# qBittorrent
|
|
QBT_HOST=http://192.168.178.x:8080
|
|
QBT_USER=...
|
|
QBT_PASSWORD=...
|
|
|
|
# *arr suite
|
|
RADARR_HOST=http://192.168.178.x:7878
|
|
RADARR_API_KEY=...
|
|
SONARR_HOST=http://192.168.178.x:8989
|
|
SONARR_API_KEY=...
|
|
LIDARR_HOST=http://192.168.178.x:8686
|
|
LIDARR_API_KEY=...
|
|
|
|
# Jellyfin
|
|
JELLYFIN_HOST=http://192.168.178.x:8096
|
|
JELLYFIN_API_KEY=...
|
|
|
|
# Navidrome
|
|
NAVIDROME_HOST=http://192.168.178.x:4533
|
|
NAVIDROME_USER=...
|
|
NAVIDROME_PASSWORD=...
|
|
|
|
# RomM
|
|
ROMM_HOST=http://192.168.178.x:7998
|
|
ROMM_USER=...
|
|
ROMM_PASSWORD=...
|
|
|
|
# Grafana
|
|
GRAFANA_HOST=http://192.168.178.x:3000
|
|
GRAFANA_TOKEN=
|
|
|
|
# Prometheus
|
|
PROMETHEUS_HOST=http://192.168.178.x:9090
|
|
|
|
# Loki
|
|
LOKI_HOST=http://192.168.178.x:3100
|
|
|
|
# Weather (wttr.in — no API key needed)
|
|
WEATHER_LOCATION='90461 Nürnberg'
|
|
|
|
# Transit (VAG PULS API — no API key needed)
|
|
TRANSIT_STOP_VAG=SCHW
|
|
|
|
# Server port
|
|
PORT=3001
|
|
```
|
|
|
|
> **Note on special characters in passwords**: The `.env` file supports shell-quoted values (`'...'` or `"..."`). The deployment script strips quotes before passing to Docker via `--env-file`.
|
|
|
|
---
|
|
|
|
## Local Development
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev
|
|
```
|
|
|
|
Runs Vite (port 5173) and the Express server (port 3001) concurrently.
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
Deployment is fully automated via Woodpecker CI on every push to `main`.
|
|
|
|
The pipeline (`.woodpecker.yml`):
|
|
1. Builds a Docker image from `Dockerfile`
|
|
2. Strips shell quotes from `.env` (Docker `--env-file` passes values literally)
|
|
3. Stops and removes the old container
|
|
4. Starts a new container with `docker run --env-file`
|
|
|
|
The `.env` file lives at `/opt/docker/dashboard/.env` on the host and is mounted into the pipeline as a secret volume — it is never committed to the repository.
|
|
|
|
---
|
|
|
|
## Adding a New App Link
|
|
|
|
Edit `src/config/apps.ts`:
|
|
|
|
```ts
|
|
{ name: 'My App', url: 'https://myapp.syco.me', icon: `${ICONS}/myapp.svg` }
|
|
```
|
|
|
|
Download the icon to `public/icons/` first. Icons are sourced from [selfh.st/icons](https://selfh.st/icons).
|
|
|
|
## Adding a New Widget
|
|
|
|
1. Create `src/components/widgets/MyWidget.tsx`
|
|
2. Add it to `src/config/dashboard.ts` in the appropriate section
|
|
|
|
---
|
|
|
|
## Changing the Transit Stop
|
|
|
|
Update `TRANSIT_STOP_VAG` in `.env` with a VAG stop ID. Look up stop IDs via:
|
|
|
|
```
|
|
https://start.vag.de/dm/api/haltestellen.json/vag?name=<stop name>
|
|
```
|