@@ -0,0 +1,247 @@
|
||||
# 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>
|
||||
```
|
||||
Reference in New Issue
Block a user