diff --git a/configs/homepage/.env.example b/configs/homepage/.env.example new file mode 100644 index 0000000..788ea54 --- /dev/null +++ b/configs/homepage/.env.example @@ -0,0 +1,20 @@ +# Homepage environment variables. +# +# Homepage substitutes any var matching `HOMEPAGE_VAR_*` or +# `HOMEPAGE_FILE_*` into any config file (services.yaml, proxmox.yaml, ...). +# Reference them in YAML with the literal string `{{HOMEPAGE_VAR_NAME}}`. +# +# Copy to .env next to the homepage compose file on esh-docker-vm. Never +# commit the real .env — it holds widget API keys. + +# Plex widget token (Plex -> account -> get X-Plex-Token) +HOMEPAGE_VAR_PLEX_KEY= + +# Jellyfin API key (Dashboard -> API Keys) +HOMEPAGE_VAR_JELLYFIN_KEY= + +# Proxmox widget tokens (one per hypervisor; see proxmox.yaml comments) +# HOMEPAGE_VAR_PFI_PVE_TOKEN= +# HOMEPAGE_VAR_NH3_PVE_TOKEN= +# HOMEPAGE_VAR_ESH_PVE_TOKEN= +# HOMEPAGE_VAR_ESH_PVE_NAS_TOKEN= diff --git a/configs/homepage/README.md b/configs/homepage/README.md new file mode 100644 index 0000000..1d22f86 --- /dev/null +++ b/configs/homepage/README.md @@ -0,0 +1,95 @@ +# homepage + +Canonical copies of the [gethomepage.dev](https://gethomepage.dev/) config +for the fleet dashboard running on `esh-docker-vm` (`10.0.50.45`). + +## What lives here + +| File | Purpose | +|---|---| +| `settings.yaml` | Title, theme, background, quick-launch, group layout | +| `services.yaml` | Manual entries — infra, BMCs, off-Docker endpoints, fleet hubs | +| `bookmarks.yaml` | External links (UltraSeedbox, etc.) | +| `widgets.yaml` | Top-of-page widgets (resource panel, search) | +| `docker.yaml` | Per-host Docker socket providers for label-based auto-discovery | +| `kubernetes.yaml`, `proxmox.yaml` | Empty / sample — kept so homepage doesn't warn on startup | +| `custom.css`, `custom.js` | Placeholders | +| `.env.example` | Template for widget secrets (Plex, Jellyfin, eventual Proxmox tokens) | + +The real `.env` (with Plex + Jellyfin keys) lives on `esh-docker-vm` next +to the compose file and is gitignored. + +## Layout convention + +`settings.yaml` drives the group layout: + +``` +Monitoring row x 3 fleet hubs (Beszel, Dozzle, Backrest, Uptime Kuma) +AI Systems row x 3 GPU inference services (llama-swap, vLLM embed/rerank) +Apps list user-facing apps (Gitea, Vaultwarden, Seafile, ...) +Media list Plex, Jellyfin +Games list Pterodactyl +UltraSeedbox row x 3 external bookmarks +Infra - ANA list Anaheim hardware + hypervisors + BMCs +Infra - NH3 list NH3 hardware + hypervisors +Infra - ESH list ESH home-lab hardware + hypervisors +Service Networking collapsed toolchain (Traefik, CrowdSec, Dockge, AdGuard, MQTT) +``` + +- **Manual entries** (this file) cover things without a Docker label: + firewalls, switches, NAS web UIs, BMCs, hypervisors, and the cross-site + hubs where direct IP:port URLs are stable. +- **Docker-labeled stacks** auto-populate their group via the providers in + `docker.yaml`. To drop a new service into a group, add + `homepage.group=` (plus `.name`, `.icon`, `.description`, `.href`) + labels to its compose file and redeploy. + +## Placement rule (for new entries) + +When deciding where a service lands, ask **function first**: + +1. Does it watch or back up the fleet? -> `Monitoring` +2. Is it an inference / model service? -> `AI Systems` +3. Is it a user-facing app? -> `Apps` +4. Is it media / games? -> `Media` or `Games` +5. Is it a piece of hardware or a hypervisor? -> `Infra - ` +6. Is it toolchain / plumbing (no human interaction on the golden path)? -> + `Service Networking` + +Site-specific sub-grouping is only used for `Infra -` because the device +inventory maps cleanly to physical sites. App groups are function-only. + +## Deploying changes + +These files are the **canonical source** for the homepage config. The +homepage compose file itself lives on `esh-docker-vm` (not yet tracked in +this repo as a stack), so the usual `scripts/deploy-stack.sh` flow doesn't +apply here yet. + +Current workflow — push this directory onto the host: + +```bash +rsync -av --delete \ + --exclude='.env' --exclude='.env.*' \ + configs/homepage/ esh-docker-vm:/opt/docker/conf/homepage/ +``` + +The real `.env` lives on `esh-docker-vm` next to the compose file and must +not be overwritten (holds Plex/Jellyfin keys). + +The homepage container reloads most files on-change; if a new group in +`settings.yaml` doesn't show up, `docker compose restart` on the host. + +**Follow-up:** once the homepage compose file is pulled into +`stacks/homepage/compose.yaml`, move these files to `stacks/homepage/conf/` +and drop this ad-hoc rsync in favor of `scripts/deploy-stack.sh`. + +## Secrets / env substitution + +Any config can reference `{{HOMEPAGE_VAR_NAME}}` and homepage will +substitute from the container env at render time. Current uses: + +- `HOMEPAGE_VAR_PLEX_KEY` (services.yaml -> Plex widget) +- `HOMEPAGE_VAR_JELLYFIN_KEY` (services.yaml -> Jellyfin widget) + +Keep these out of the tracked YAML; only `.env.example` ships the names. diff --git a/configs/homepage/bookmarks.yaml b/configs/homepage/bookmarks.yaml new file mode 100644 index 0000000..8df6eae --- /dev/null +++ b/configs/homepage/bookmarks.yaml @@ -0,0 +1,43 @@ +# https://gethomepage.dev/latest/configs/bookmarks + +- UltraSeedbox: + - Control Panel: + - abbr: CP + icon: si-bittorrent + href: http://cp.ultra.cc/ + description: Ultraseedbox Control Panel + - FileZilla: + - abbr: fz + icon: si-filezilla + href: http://10.0.50.154:5800 + description: FTP Client + - ruTorrent: + - abbr: rt + icon: si-bittorrent + href: https://lkraven.hebe.usbx.me/rutorrent/ + description: ruTorrent Client + - Sonarr: + - abbr: sr + icon: si-sonarr + href: https://lkraven.hebe.usbx.me/sonarr/ + description: TV Shows + - Radarr: + - abbr: rr + icon: si-radarr + href: https://lkraven.hebe.usbx.me/radarr/ + description: Movies + - Bazarr: + - abbr: br + icon: mdi-subtitles + href: https://lkraven.hebe.usbx.me/bazarr/ + description: Subtitles + - Sabnzbd: + - abbr: nz + icon: mdi-newspaper + href: https://lkraven.hebe.usbx.me/sabnzbd + description: Newz Client + - Seedsync: + - abbr: sy + icon: mdi-download-box + href: http://10.0.50.51:8800/dashboard + description: Sync Client diff --git a/configs/homepage/custom.css b/configs/homepage/custom.css new file mode 100644 index 0000000..fcc7889 --- /dev/null +++ b/configs/homepage/custom.css @@ -0,0 +1,2 @@ +/* Custom styling for homepage. Empty placeholder — keep file present so + * homepage doesn't 404 on the asset. */ diff --git a/configs/homepage/custom.js b/configs/homepage/custom.js new file mode 100644 index 0000000..b377919 --- /dev/null +++ b/configs/homepage/custom.js @@ -0,0 +1,2 @@ +// Custom JS for homepage. Empty placeholder — keep file present so +// homepage doesn't 404 on the asset. diff --git a/configs/homepage/docker.yaml b/configs/homepage/docker.yaml new file mode 100644 index 0000000..17986ca --- /dev/null +++ b/configs/homepage/docker.yaml @@ -0,0 +1,31 @@ +--- +# https://gethomepage.dev/latest/configs/docker/ +# +# All four Docker hosts expose the engine on port 2375. This is plaintext +# and works fine on a trusted LAN; if any host is ever exposed or shared, +# switch to a TLS socket (examples commented below). + +esh-vm-docker: + host: 10.0.50.45 + port: 2375 + +ana-pfi-docker: + host: 10.250.50.70 + port: 2375 + +nh3-pfi-docker: + host: 10.100.50.40 + port: 2375 + +ana-ml2-docker: + host: 10.250.50.54 + port: 2375 + +# Example TLS socket (if/when a host moves off plaintext 2375): +# ana-pfi-docker: +# host: 10.250.50.70 +# port: 2376 +# tls: +# keyFile: docker-tls/ana-docker/key.pem +# caFile: docker-tls/ana-docker/ca.pem +# certFile: docker-tls/ana-docker/cert.pem diff --git a/configs/homepage/kubernetes.yaml b/configs/homepage/kubernetes.yaml new file mode 100644 index 0000000..50473d7 --- /dev/null +++ b/configs/homepage/kubernetes.yaml @@ -0,0 +1,4 @@ +--- +# https://gethomepage.dev/latest/configs/kubernetes +# No Kubernetes cluster in the fleet. Keep file present so homepage +# doesn't warn on startup. diff --git a/configs/homepage/proxmox.yaml b/configs/homepage/proxmox.yaml new file mode 100644 index 0000000..65f9c9e --- /dev/null +++ b/configs/homepage/proxmox.yaml @@ -0,0 +1,14 @@ +--- +# https://gethomepage.dev/latest/widgets/services/proxmox +# Not wired up yet. To use per-node widgets, create an API token on each +# PVE node (Datacenter -> Permissions -> API Tokens) and define an entry +# per hypervisor here: +# +# pfi-pve: +# url: https://10.250.250.31:8006 +# token: 'homepage@pam!hp' +# secret: '{{HOMEPAGE_VAR_PFI_PVE_TOKEN}}' +# +# nh3-pve: ... +# esh-pve: ... +# esh-pve-nas: ... diff --git a/configs/homepage/services.yaml b/configs/homepage/services.yaml new file mode 100644 index 0000000..67d9257 --- /dev/null +++ b/configs/homepage/services.yaml @@ -0,0 +1,165 @@ +--- +# https://gethomepage.dev/latest/configs/services +# +# Groups are ordered by settings.yaml `layout`. This file holds the manual +# entries — infrastructure, BMCs, off-Docker endpoints, and a handful of +# well-known internal service URLs. Docker-labeled stacks auto-populate +# their own groups through the providers in docker.yaml, so most app +# entries don't need to appear here. + +- Monitoring: + # Beszel, Dozzle, Backrest arrive here via Docker label auto-discovery + # (homepage.group=Monitoring on their compose files). Do not re-add them + # manually — they'll double up in the UI. + - Uptime Kuma: + href: http://10.0.50.45:3001 + icon: mdi-check-network + siteMonitor: http://10.0.50.45:3001 + description: Uptime monitor (esh-docker-vm) + +# AI Systems group is fully Docker-auto-discovered (llama-swap, vLLM Embed, +# vLLM Rerank — homepage.group=AI Systems on their compose files). Position +# and row×3 style for the group live in settings.yaml. Do not add entries +# here or they'll double up. + +- Media: + - Plex: + href: http://10.0.50.56:32400 + icon: si-plex + siteMonitor: http://10.0.50.56:32400 + description: Media Server (esh-nas-pve 10.0.50.56) + widget: + type: plex + url: http://10.0.50.56:32400 + key: '{{HOMEPAGE_VAR_PLEX_KEY}}' + - Jellyfin: + href: http://10.0.50.57:8096 + icon: si-jellyfin + siteMonitor: http://10.0.50.57:8096 + description: Media Server (esh-nas-pve 10.0.50.57) + widget: + type: jellyfin + url: http://10.0.50.57:8096 + key: '{{HOMEPAGE_VAR_JELLYFIN_KEY}}' + enableBlocks: true + +- Games: + - Pterodactyl: + href: http://10.250.50.55/ + icon: mdi-gamepad-square + siteMonitor: http://10.250.50.55 + description: Game server panel + +- Infra - ANA: + - ANA-Firewall: + href: https://ana-fw.phasefinal.com/ + icon: mdi-wall-fire + siteMonitor: https://10.250.250.1 + description: ana-gw Fortigate 81F + - PFI-r750xs-iDRAC: + href: https://10.250.250.30/ + siteMonitor: https://10.250.250.30/ + icon: si-dell + description: iDRAC (Dell R750xs) + - PFI-PVE: + href: https://10.250.250.31:8006/ + siteMonitor: https://10.250.250.31:8006/ + icon: si-proxmox + description: Proxmox hypervisor (pfi-pve) + - ANA-NAS: + href: https://10.250.50.50:9090/ + siteMonitor: https://10.250.50.50:9090/ + icon: mdi-nas + description: Debian NAS (Cockpit) + - ANA-FileBot: + ping: 10.250.50.53 + icon: mdi-sync-circle + description: File-task VM + - PFI-VM-Docker: + href: http://10.250.50.70:5001 + icon: si-docker + ping: 10.250.50.70 + description: Docker VM (ana-docker, Dockge at :5001) + - PFI-ANA-ML2: + ping: 10.250.50.54 + icon: mdi-brain + description: GPU host (bare-metal) + - PFI-ANA-ML2 BMC: + ping: 10.250.250.50 + icon: mdi-brain + description: BMC (ana-ml2) + +- Infra - NH3: + - NH3-Firewall: + href: https://10.100.250.1 + icon: mdi-wall-fire + siteMonitor: https://10.100.250.1 + description: nh3-gw Fortigate 101F + - NH3-SW1: + href: https://10.100.250.2 + icon: mdi-switch + ping: 10.100.250.2 + description: Mikrotik CRS328-24P-4S+ + - NH3-NAS: + href: https://10.100.50.50:5001 + icon: mdi-nas + siteMonitor: https://10.100.50.50:5001 + description: Synology RS2418+ DSM (restic target + VM storage) + - NH3-PVE: + href: https://10.100.250.60:8006/ + siteMonitor: https://10.100.250.60:8006/ + icon: si-proxmox + description: Proxmox hypervisor (nh3-pve) + - NH3-VM-Docker: + href: http://10.100.50.40:5001 + icon: si-docker + ping: 10.100.50.40 + description: Docker VM (nh3-docker, Dockge at :5001) + - NH3-Ansible: + ping: 10.100.50.42 + icon: si-ansible + description: Ansible control node + +- Infra - ESH: + - ESH-Firewall: + href: https://10.0.250.1 + icon: mdi-wall-fire + siteMonitor: https://10.0.250.1 + description: esh-gw + - Brother Printer: + href: http://10.0.90.125/ + icon: mdi-printer + siteMonitor: http://10.0.90.125/ + description: Brother (ESH) + - ESH-NAS: + href: https://10.0.50.50:9090 + icon: mdi-nas + siteMonitor: https://10.0.50.50:9090 + description: NAS share manager (Cockpit) + - ESH-PVE: + href: https://10.0.250.35:8006 + siteMonitor: https://10.0.250.35:8006 + icon: si-proxmox + description: Proxmox hypervisor (esh-pve) + - ESH-PVE-NAS: + href: https://10.0.50.55:8006 + siteMonitor: https://10.0.50.55:8006 + icon: si-proxmox + description: Proxmox hypervisor (esh-pve-nas, storage/media) + - ESH-FileBot: + ping: 10.0.50.70 + icon: mdi-sync-circle + description: Restic / file-sync VM (esh-nas-pve) — role TBC + - ESH-VM-Docker: + href: http://10.0.50.45:5001 + icon: si-docker + ping: 10.0.50.45 + description: Docker VM (esh-docker-vm, Dockge at :5001) + +# Service Networking group is now fully Docker-auto-discovered (Traefik ×2, +# AdGuard ×2, Dockge ×5, rest-server-ana, mailrise, etc. all carry +# homepage.group=Service Networking on their compose files). Position and +# layout live in settings.yaml. Do not add entries here or they'll double up. +# +# Mosquitto (ESH) note: still labeled Apps on its compose; once moved to +# Service Networking via a label change on esh-docker-vm, it auto-populates. diff --git a/configs/homepage/settings.yaml b/configs/homepage/settings.yaml new file mode 100644 index 0000000..52f679c --- /dev/null +++ b/configs/homepage/settings.yaml @@ -0,0 +1,43 @@ +--- +# https://gethomepage.dev/latest/configs/settings +title: ... all my base ... +theme: dark +color: slate +background: + image: https://w.wallhaven.cc/full/p9/wallhaven-p9gr2p.jpg + blur: sm + opacity: 20 +headerStyle: boxedWidgets + +providers: + openweathermap: openweathermapapikey + weatherapi: weatherapiapikey + +quicklaunch: + searchDescriptions: true + hideInternetSearch: true + showSearchSuggestions: true + hideVisitURL: true + +showStats: false +statusStyle: "" + +# Function-first layout. Site-partitioned Infra groups keep the inventory +# together without mixing app/infra concerns. +layout: + Monitoring: + style: row + columns: 3 + AI Systems: + style: row + columns: 3 + Apps: + Media: + Games: + UltraSeedbox: + style: row + columns: 3 + Infra - ANA: + Infra - NH3: + Infra - ESH: + Service Networking: diff --git a/configs/homepage/widgets.yaml b/configs/homepage/widgets.yaml new file mode 100644 index 0000000..63e4b15 --- /dev/null +++ b/configs/homepage/widgets.yaml @@ -0,0 +1,11 @@ +--- +# https://gethomepage.dev/latest/configs/service-widgets + +- resources: + cpu: true + memory: true + disk: / + +- search: + provider: duckduckgo + target: _blank diff --git a/stacks/backrest/compose.yaml b/stacks/backrest/compose.yaml index 4e5213c..cf28175 100644 --- a/stacks/backrest/compose.yaml +++ b/stacks/backrest/compose.yaml @@ -42,7 +42,7 @@ services: networks: - tnet labels: - - homepage.group=PFI-ANA + - homepage.group=Monitoring - homepage.name=Backrest - homepage.icon=mdi-backup-restore - homepage.description=Restic snapshot viewer / restore UI diff --git a/stacks/beszel/compose.yaml b/stacks/beszel/compose.yaml index dafadf3..8249178 100644 --- a/stacks/beszel/compose.yaml +++ b/stacks/beszel/compose.yaml @@ -6,7 +6,8 @@ # Multi-host layout via compose profiles: # COMPOSE_PROFILES=hub → hub only (ana-docker) # COMPOSE_PROFILES=hub,agent → hub + local agent on the same host -# COMPOSE_PROFILES=agent → agent only (ana-ml2) +# COMPOSE_PROFILES=agent → agent only (ana-ml2, nh3-docker, +# esh-docker-vm, vm-esh-nas) # # The agent uses network_mode: host so it sees real host CPU/mem/net/disk # counters rather than container-scoped ones — that's why it can't share @@ -25,15 +26,17 @@ services: volumes: - beszel_data:/beszel_data healthcheck: - test: ["CMD", "wget", "-qO-", "http://localhost:8090/api/health"] - interval: 30s + # Hub image is distroless — no wget/curl. Use the bundled `/beszel` + # binary's built-in health subcommand (https://beszel.dev/guide/healthchecks). + test: ["CMD", "/beszel", "health", "--url", "http://localhost:8090"] + interval: 120s timeout: 10s retries: 3 start_period: 15s networks: - tnet labels: - - homepage.group=PFI-ANA + - homepage.group=Monitoring - homepage.name=Beszel - homepage.icon=mdi-chart-line - homepage.description=Server + container monitoring @@ -49,9 +52,25 @@ services: - /var/run/docker.sock:/var/run/docker.sock:ro - beszel_agent_data:/var/lib/beszel-agent environment: + # Agent auth has two modes (v0.13+ supports both side-by-side): + # - KEY-mode: agent listens, hub connects inbound over SSH using KEY. + # Requires BESZEL_HUB_KEY in .env. + # - Token-mode: agent initiates an outbound connection to HUB_URL + # using TOKEN. Easier through NAT. Requires HUB_URL + BESZEL_TOKEN. + # Leave unused ones empty ("") in .env; both can be set simultaneously. - PORT=${BESZEL_AGENT_PORT:-45876} - - KEY=${BESZEL_HUB_KEY} + - KEY=${BESZEL_HUB_KEY:-} + - HUB_URL=${HUB_URL:-} + - TOKEN=${BESZEL_TOKEN:-} - EXTRA_FILESYSTEMS=${BESZEL_EXTRA_FS:-} + healthcheck: + # Agent image ships the `/agent` binary with a `health` subcommand. + # Verifies the agent process is up — not that the hub can reach it. + test: ["CMD", "/agent", "health"] + interval: 120s + timeout: 10s + retries: 3 + start_period: 15s volumes: beszel_data: diff --git a/stacks/dozzle/compose.yaml b/stacks/dozzle/compose.yaml index 59f82d3..1ff10d5 100644 --- a/stacks/dozzle/compose.yaml +++ b/stacks/dozzle/compose.yaml @@ -34,7 +34,7 @@ services: networks: - tnet labels: - - homepage.group=PFI-ANA + - homepage.group=Monitoring - homepage.name=Dozzle - homepage.icon=mdi-text-box-search - homepage.description=Container logs (ana-docker + ana-ml2) diff --git a/stacks/rest-server-ana/compose.yaml b/stacks/rest-server-ana/compose.yaml index d31d70a..1d14c30 100644 --- a/stacks/rest-server-ana/compose.yaml +++ b/stacks/rest-server-ana/compose.yaml @@ -40,7 +40,11 @@ services: - OPTIONS=--private-repos --append-only --prometheus ${EXTRA_OPTIONS:-} - TZ=${TZ:-America/Los_Angeles} healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:8000/metrics >/dev/null 2>&1 || [ $? -eq 6 ] && exit 0 || exit 1"] + # TCP port probe — busybox nc ships in the restic/rest-server Alpine + # image. HTTP-level checks fight with --private-repos returning 401 + # (busybox wget exit 8, gnu wget exit 6 — version-dependent and hard + # to branch on cleanly in POSIX sh). + test: ["CMD", "nc", "-z", "localhost", "8000"] interval: 30s timeout: 10s retries: 3 @@ -48,7 +52,7 @@ services: networks: - tnet labels: - - homepage.group=PFI-ANA + - homepage.group=Service Networking - homepage.name=Restic (rest-server) - homepage.icon=mdi-cloud-upload - homepage.description=Anaheim restic endpoint (data on NFS) diff --git a/stacks/rest-server-nh3/compose.yaml b/stacks/rest-server-nh3/compose.yaml index 43a89f9..dd697dd 100644 --- a/stacks/rest-server-nh3/compose.yaml +++ b/stacks/rest-server-nh3/compose.yaml @@ -33,7 +33,9 @@ services: # automatically — no extra bind mount needed as long as the htpasswd # file is created inside DATA_DIR before startup. healthcheck: - test: ["CMD-SHELL", "wget -qO- http://localhost:8000/metrics >/dev/null || exit 1"] + # TCP port probe — avoids the --private-repos 401 trap. + # See stacks/rest-server-ana/compose.yaml for reasoning. + test: ["CMD", "nc", "-z", "localhost", "8000"] interval: 30s timeout: 10s retries: 3