# 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= ```