Syco 638c7d524f
ci/woodpecker/push/woodpecker Pipeline was successful
Make header sticky at top of viewport
2026-05-16 18:06:17 +02:00
2026-05-16 18:06:17 +02:00
2026-05-10 21:23:42 +02:00
2026-05-09 14:14:51 +02:00
2026-05-14 10:41:14 +02:00
2026-05-10 21:23:42 +02:00
2026-05-16 16:44:45 +02:00
2026-05-10 21:23:42 +02:00
2026-05-10 21:23:42 +02:00
2026-05-10 21:23:42 +02:00

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 — no API key needed
Transit VAG PULS 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.

# 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

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.


Edit src/config/apps.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.

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>
S
Description
No description provided
Readme 521 KiB
Languages
TypeScript 88.5%
CSS 10.8%
HTML 0.4%
Dockerfile 0.3%