Homepage
⏳ Doba čtení: ~10 min (1279 slov)Homepage dashboard
Když se vám na domácím serveru začnou kupit služby jako Jellyfin, Kavita, Audiobookshelf a lokální LLM přes OpenWebUI, dříve nebo později zjistíte, že potřebujete centrální rozcestník. Na Zalmanovi toho běží už docela dost a pamatovat si všechny porty přestává být reálné (alespoň pro Eriku). Rozhodl jsem se nasadit aplikaci Homepage. Je rychlá, moderní a hlavně kompletně se řídí přes YAML soubory. Žádné klikací GUI, zkrátka ideální adept pro plnou automatizaci přes Ansible.
Tady je shrnutí toho, jak jsem postupoval, a hlavně řešení několika zajímavých pastí, na které jsem cestou narazil.
Prvotní nasazení a ochrana hostitele
Jelikož vše nasazuji přes Ansible, recykloval jsem už existující šablonu pro systemd službu a Docker Compose. Oproti aplikacím jako Audiobookshelf jsem mohl z playbooku vyhodit připojování sdílených SMB složek. Homepage potřebuje pouze lokální adresář pro konfiguraci.
Mapování SMB disků (pouze pro čtení) do kontejneru vracím, abych mohl v dashboardu zobrazit, kolik místa na discích zbývá a kolik je obsazeno.
První zádrhel přišel hned po startu kontejneru. Místo hezkého webu na mě vyskočila chyba Host validation failed. Ukázalo se, že autoři Homepage nedávno přidali bezpečnostní ochranu proti podvržení hlavičky hostitele. Kontejner zkrátka ignoruje požadavky, pokud mu explicitně neřeknete, na jaké IP adrese nebo doméně reálně poslouchá. Stačilo do docker-compose.yml přidat proměnnou prostředí:
# Add explicitly allowed IPs and hostnames
environment:
- HOMEPAGE_ALLOWED_HOSTS=192.168.1.101:3000,zalman:3000
Boj s oprávněními a past jménem docker.sock
Homepage umí u každé služby zobrazit, jestli kontejner běží a kolik žere paměti či procesoru. K tomu ale potřebuje přístup k Docker daemonu. Připojil jsem tedy do kontejneru /var/run/docker.sock, ale aplikace neustále házela chybu EACCES.
Problém je v právech. Homepage z bezpečnostních důvodů startuje jako neprivilegovaný uživatel (v mém případě přes proměnné PUID=1108 a PGID=2000). Zjistil jsem si tedy přes stat -c '%g' /var/run/docker.sock, že na hostiteli má skupina docker ID 103, a přidal jsem do compose souboru direktivu group_add: ["103"].
Tohle normálně funguje, ale ne tady. Startovací skript v kontejneru totiž nejprve jako root srovná oprávnění lokálních složek a pak přes su-exec zahodí práva a přepne se pod UID 1108. Během tohoto zahození práv ale proces ztratí dodatečné skupiny definované přes group_add. Z pohledu bezpečnosti je nesmysl dělat na socketu hostitele chmod 666, takže jsem sáhl po čistším sysadmin řešení – Docker Socket Proxy.
Přidal jsem do stacku malý proxy kontejner, který jako jediný čte socket a dovnitř interní Docker sítě ho vystavuje jako read-only API na portu 2375.
# Secure way to access Docker API without messing with permissions
services:
docker-proxy:
image: tecnativa/docker-socket-proxy:latest
container_name: homepage-docker-proxy
environment:
- CONTAINERS=1
- POST=0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
V konfiguraci Homepage (docker.yaml) pak stačilo nastavit napojení na proxy a bylo po problému.
---
docker-runner:
host: docker-proxy
port: 2375
Záludnosti striktního YAML parseru
Když se řeší Infrastructure as Code, člověk občas narazí na to, jak striktní některé parsery umí být. Při definování služeb v services.yaml mi Homepage několik položek s chybou Error parsing service úplně zahodila a vůbec nevykreslila.
Důvody byly dva:
- Odsazení: Vlastnosti jako
iconnebohrefnesmí být na stejné úrovni jako název služby, jinak si YAML parser myslí, že je uzel prázdný. - Ping s portem: U OpenWebUI jsem chtěl testovat dostupnost přes parametr
ping. Napsal jsem tam192.168.1.84:3000. Jenže ping na pozadí volá ICMP, které logicky žádné porty nezná, takže dotaz selhal. Bylo potřeba vynutit HTTP vrstvu přidáním protokolu na začátek ([http://192.168.1.84:3000](http://192.168.1.84:3000)).
Menší tip pro ikony. Místo toho, abych stahoval PNG obrázky log (třeba pro přístupový bod T-Mobile) a mapoval je do kontejneru, využil jsem nativní podporu Simple Icons. Stačí do konfigurace zadat icon: si-tmobile a Homepage si příslušné SVG logo dotáhne sama.
Když Proxmox a Jellyfin API mlčí
Chtěl jsem mít v dashboardu nejen odkazy, ale i živá data (API widgety).
U Jellyfinu mi dashboard začal chrlit chyby HTTP 404. Zjistil jsem, že Jellyfin od verze 10.9.0 trvale zařízl staré zpětně kompatibilní /emby/ endpointy, které Homepage ve výchozím stavu používá. Stačilo do konfigurace widgetu přidat řádek version: 2, aby aplikace přešla na nové API.
Větší záhadou byl Proxmox. Widget do něj sice viděl, ale sveřepě ukazoval zátěž NaN% a 0 VMS. První chyba byla, že jsem mu neřekl, jaký fyzický server v clusteru má číst (nutnost přidat node: zalman). Druhá past se ukrývala přímo v Proxmox GUI. Když vytváříte API token pro externí aplikaci a necháte aktivní volbu “Privilege Separation”, token ve výchozím stavu nemá vůbec žádná oprávnění. Proxmox neumožňuje přiřadit roli přímo při tvorbě tokenu. Musíte jít do sekce Datacenter -> Permissions, přidat nové pravidlo na cestu /, vybrat váš token a explicitně mu přidělit roli PVEAuditor (pouze pro čtení).
Po uložení se rázem načetly reálné statistiky CPU a RAM celého serveru.
Ukázka services.yaml:
---
- Infrastructure:
- Proxmox:
icon: proxmox.png
href: https://<IP>:8006
description: Virtualizace a kontejnery
ping: <IP>
widget:
type: proxmox
url: https://<IP>:8006
username: root@pam!Homepage
password: <API_TOKEN>
node: zalman
ignoreTls: true
- "T-mobile router":
icon: si-tmobile
href: https://192.168.1.1
description: T-mobile pristupovy bod
ping: 192.168.1.1
- Media:
- Audiobookshelf:
icon: audiobookshelf.png
href: http://<IP>:13378
description: Audioknihy a podcasty
server: docker-runner
container: audiobookshelf
widget:
type: audiobookshelf
url: http://<IP>:13378
key: <API_KEY>
Záhlaví na stránce
Soubor widget.yaml definuje co se má zobrazit v horní části stránky.
---
- resources:
label: Docker runner
cpu: true
memory: true
disk: /
units: metric
- resources:
label: Data pool
expanded: true
disk:
- /mnt/data
- resources:
label: Movies pool
expanded: true
disk:
- /mnt/movies
Zde jsem narazil na chybějící mounty SMB disků, které se nepropagují do kontejneru. V Ansible roli jsem je tedy přidal přes
volumesa vwidget.yamlpak použil cestu/mnt/dataa/mnt/movies.
Závěr
Obalit celou konfiguraci do Ansible repozitáře dalo trochu práce, hlavně kvůli ladění správného přístupu na Docker socket a pochopení specifik jednotlivých API. Výsledek za to ale stojí. Mám teď plně automatizovaný rozcestník, který nejenže dobře vypadá, ale rovnou mi ukazuje aktivní streamy, počty knih i zdraví celé infrastruktury, aniž bych musel ručně spravovat jediný soubor přímo na serveru.