Sanitized mirror from private repository - 2026-04-20 01:32:01 UTC
This commit is contained in:
0
hosts/physical/anubis/.gitkeep
Normal file
0
hosts/physical/anubis/.gitkeep
Normal file
22
hosts/physical/anubis/archivebox.yml
Normal file
22
hosts/physical/anubis/archivebox.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
# docker-compose run archivebox init --setup
|
||||
# docker-compose up
|
||||
# echo "https://example.com" | docker-compose run archivebox archivebox add
|
||||
# docker-compose run archivebox add --depth=1 https://example.com/some/feed.rss
|
||||
# docker-compose run archivebox config --set PUBLIC_INDEX=True
|
||||
# docker-compose run archivebox help
|
||||
# Documentation:
|
||||
# https://github.com/ArchiveBox/ArchiveBox/wiki/Docker#docker-compose
|
||||
|
||||
version: '2.4'
|
||||
|
||||
services:
|
||||
archivebox:
|
||||
image: archivebox/archivebox:master
|
||||
command: server --quick-init 0.0.0.0:8000
|
||||
ports:
|
||||
- 8000:8000
|
||||
environment:
|
||||
- ALLOWED_HOSTS=*
|
||||
- MEDIA_MAX_SIZE=750m
|
||||
volumes:
|
||||
- ./data:/data
|
||||
17
hosts/physical/anubis/chatgpt.yml
Normal file
17
hosts/physical/anubis/chatgpt.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# ChatGPT Web - AI chat
|
||||
# Port: 3000
|
||||
# ChatGPT web interface
|
||||
|
||||
version: '3.9'
|
||||
services:
|
||||
deiucanta:
|
||||
image: 'ghcr.io/deiucanta/chatpad:latest'
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '5690:80'
|
||||
container_name: Chatpad-AI
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
30
hosts/physical/anubis/conduit.yml
Normal file
30
hosts/physical/anubis/conduit.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Conduit - Matrix server
|
||||
# Port: 6167
|
||||
# Lightweight Matrix homeserver
|
||||
|
||||
version: "3.9"
|
||||
services:
|
||||
matrix-conduit:
|
||||
image: matrixconduit/matrix-conduit:latest
|
||||
container_name: Matrix-Conduit
|
||||
hostname: matrix-conduit
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
user: 1000:1000
|
||||
ports:
|
||||
- "8455:6167"
|
||||
volumes:
|
||||
- "/volume1/docker/matrix-conduit:/var/lib/matrix-conduit/"
|
||||
environment:
|
||||
- CONDUIT_SERVER_NAME=vishtestingserver
|
||||
- CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit/
|
||||
- CONDUIT_DATABASE_BACKEND=rocksdb
|
||||
- CONDUIT_PORT=6167
|
||||
- CONDUIT_MAX_REQUEST_SIZE=20000000
|
||||
- CONDUIT_ALLOW_REGISTRATION=true
|
||||
- CONDUIT_ALLOW_FEDERATION=true
|
||||
- CONDUIT_TRUSTED_SERVERS=["matrix.org"]
|
||||
- CONDUIT_MAX_CONCURRENT_REQUESTS=250
|
||||
- CONDUIT_ADDRESS=0.0.0.0
|
||||
- CONDUIT_CONFIG=''
|
||||
restart: unless-stopped
|
||||
9
hosts/physical/anubis/draw.io.yml
Normal file
9
hosts/physical/anubis/draw.io.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
version: '3.9'
|
||||
services:
|
||||
drawio:
|
||||
image: jgraph/drawio
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '8443:8443'
|
||||
- '5022:8080'
|
||||
container_name: drawio
|
||||
15
hosts/physical/anubis/element.yml
Normal file
15
hosts/physical/anubis/element.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# Element Web - Matrix client
|
||||
# Port: 80
|
||||
# Matrix chat web client
|
||||
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
element-web:
|
||||
image: vectorim/element-web:latest
|
||||
container_name: element-web
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /home/vish/docker/elementweb/config.json:/app/config.json
|
||||
ports:
|
||||
- 9000:80
|
||||
88
hosts/physical/anubis/photoprism.yml
Normal file
88
hosts/physical/anubis/photoprism.yml
Normal file
@@ -0,0 +1,88 @@
|
||||
# PhotoPrism - Photo management
|
||||
# Port: 2342
|
||||
# AI-powered photo management
|
||||
|
||||
version: "3.9"
|
||||
services:
|
||||
db:
|
||||
image: mariadb:jammy
|
||||
container_name: PhotoPrism-DB
|
||||
hostname: photoprism-db
|
||||
mem_limit: 1g
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
- seccomp:unconfined
|
||||
- apparmor:unconfined
|
||||
user: 1000:1000
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mysqladmin ping -u root -p$$MYSQL_ROOT_PASSWORD | grep 'mysqld is alive' || exit 1"]
|
||||
volumes:
|
||||
- /home/vish/docker/photoprism/db:/var/lib/mysql:rw
|
||||
environment:
|
||||
TZ: America/Los_Angeles
|
||||
MYSQL_ROOT_PASSWORD: "REDACTED_PASSWORD"
|
||||
MYSQL_DATABASE: photoprism
|
||||
MYSQL_USER: photoprism-user
|
||||
MYSQL_PASSWORD: "REDACTED_PASSWORD"
|
||||
restart: on-failure:5
|
||||
|
||||
photoprism:
|
||||
image: photoprism/photoprism:latest
|
||||
container_name: PhotoPrism
|
||||
hostname: photoprism
|
||||
mem_limit: 6g
|
||||
cpu_shares: 1024
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
- seccomp:unconfined
|
||||
- apparmor:unconfined
|
||||
user: 1000:1009
|
||||
healthcheck:
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:2342
|
||||
ports:
|
||||
- 2342:2342
|
||||
volumes:
|
||||
- /home/vish/docker/photoprism/import:/photoprism/import:rw # *Optional* base folder from which files can be imported to originals
|
||||
- /home/vish/docker/photoprism/storage:/photoprism/storage:rw
|
||||
- /home/vish/docker/photoprism/originals:/photoprism/originals:rw
|
||||
# - /volume1/docker/photoprism/family:/photoprism/originals/family:rw # *Additional* media folders can be mounted like this
|
||||
environment:
|
||||
PHOTOPRISM_ADMIN_USER: vish
|
||||
PHOTOPRISM_ADMIN_PASSWORD: "REDACTED_PASSWORD"
|
||||
PHOTOPRISM_UID: 1000
|
||||
PHOTOPRISM_GID: 1000
|
||||
PHOTOPRISM_AUTH_MODE: password
|
||||
PHOTOPRISM_SITE_URL: http://localhost:2342/
|
||||
PHOTOPRISM_ORIGINALS_LIMIT: 5120
|
||||
PHOTOPRISM_HTTP_COMPRESSION: gzip
|
||||
PHOTOPRISM_READONLY: false
|
||||
PHOTOPRISM_EXPERIMENTAL: false
|
||||
PHOTOPRISM_DISABLE_CHOWN: false
|
||||
PHOTOPRISM_DISABLE_WEBDAV: false
|
||||
PHOTOPRISM_DISABLE_SETTINGS: false
|
||||
PHOTOPRISM_DISABLE_TENSORFLOW: false
|
||||
PHOTOPRISM_DISABLE_FACES: false
|
||||
PHOTOPRISM_DISABLE_CLASSIFICATION: false
|
||||
PHOTOPRISM_DISABLE_RAW: false
|
||||
PHOTOPRISM_RAW_PRESETS: false
|
||||
PHOTOPRISM_JPEG_QUALITY: 100
|
||||
PHOTOPRISM_DETECT_NSFW: false
|
||||
PHOTOPRISM_UPLOAD_NSFW: true
|
||||
PHOTOPRISM_SPONSOR: true
|
||||
PHOTOPRISM_DATABASE_DRIVER: mysql
|
||||
PHOTOPRISM_DATABASE_SERVER: photoprism-db:3306
|
||||
PHOTOPRISM_DATABASE_NAME: photoprism
|
||||
PHOTOPRISM_DATABASE_USER: photoprism-user
|
||||
PHOTOPRISM_DATABASE_PASSWORD: "REDACTED_PASSWORD"
|
||||
PHOTOPRISM_WORKERS: 2
|
||||
PHOTOPRISM_THUMB_FILTER: blackman # best to worst: blackman, lanczos, cubic, linear
|
||||
PHOTOPRISM_APP_MODE: standalone # progressive web app MODE - fullscreen, standalone, minimal-ui, browser
|
||||
# PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||
# PHOTOPRISM_SITE_DESCRIPTION: ""
|
||||
# PHOTOPRISM_SITE_AUTHOR: ""
|
||||
working_dir: "/photoprism"
|
||||
restart: on-failure:5
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_started
|
||||
24
hosts/physical/anubis/pialert.yml
Normal file
24
hosts/physical/anubis/pialert.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
# Pi.Alert - Network scanner
|
||||
# Port: 20211
|
||||
# Network device monitoring
|
||||
|
||||
version: "3.9"
|
||||
services:
|
||||
pi.alert:
|
||||
container_name: Pi.Alert
|
||||
healthcheck:
|
||||
test: curl -f http://localhost:17811/ || exit 1
|
||||
mem_limit: 2g
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
volumes:
|
||||
- /home/vish/docker/pialert/config:/home/pi/pialert/config:rw
|
||||
- /home/vish/docker/pialert/db:/home/pi/pialert/db:rw
|
||||
- /home/vish/docker/pialert/logs:/home/pi/pialert/front/log:rw
|
||||
environment:
|
||||
TZ: America/Los_Angeles
|
||||
PORT: 17811
|
||||
network_mode: host
|
||||
restart: on-failure:5
|
||||
image: jokobsk/pi.alert:latest
|
||||
65
hosts/physical/anubis/proxitok.yml
Normal file
65
hosts/physical/anubis/proxitok.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
# ProxiTok - TikTok frontend
|
||||
# Port: 8080
|
||||
# Privacy-respecting TikTok viewer
|
||||
|
||||
version: "3.9"
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
command: redis-server --save 60 1 --loglevel warning
|
||||
container_name: ProxiTok-REDIS
|
||||
hostname: proxitok-redis
|
||||
mem_limit: 256m
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
read_only: true
|
||||
user: 1000:1000
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping || exit 1"]
|
||||
restart: on-failure:5
|
||||
|
||||
signer:
|
||||
image: ghcr.io/pablouser1/signtok:master
|
||||
container_name: ProxiTok-SIGNER
|
||||
hostname: proxitok-signer
|
||||
mem_limit: 512m
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
read_only: true
|
||||
user: 1000:1000
|
||||
healthcheck:
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
|
||||
restart: on-failure:5
|
||||
|
||||
proxitok:
|
||||
image: ghcr.io/pablouser1/proxitok:master
|
||||
container_name: ProxiTok
|
||||
hostname: proxitok
|
||||
mem_limit: 1g
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
healthcheck:
|
||||
test: stat /etc/passwd || exit 1
|
||||
ports:
|
||||
- 9770:80
|
||||
volumes:
|
||||
- proxitok-cache:/cache
|
||||
environment:
|
||||
LATTE_CACHE: /cache
|
||||
API_CACHE: redis
|
||||
REDIS_HOST: proxitok-redis
|
||||
REDIS_PORT: 6379
|
||||
API_SIGNER: remote
|
||||
API_SIGNER_URL: http://proxitok-signer:8080/signature
|
||||
restart: on-failure:5
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
signer:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
proxitok-cache:
|
||||
145
hosts/physical/concord-nuc/README.md
Normal file
145
hosts/physical/concord-nuc/README.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Concord NUC
|
||||
|
||||
**Hostname**: concord-nuc / vish-concord-nuc
|
||||
**IP Address**: 192.168.68.100 (static, eno1)
|
||||
**Tailscale IP**: 100.72.55.21
|
||||
**OS**: Ubuntu (cloud-init based)
|
||||
**SSH**: `ssh vish-concord-nuc` (via Tailscale — see `~/.ssh/config`)
|
||||
|
||||
---
|
||||
|
||||
## Network Configuration
|
||||
|
||||
### Static IP Setup
|
||||
|
||||
`eno1` is configured with a **static IP** (`192.168.68.100/22`) via netplan. This is required because AdGuard Home binds its DNS listener to a specific IP, and DHCP lease changes would cause it to crash.
|
||||
|
||||
**Netplan config**: `/etc/netplan/50-cloud-init.yaml`
|
||||
|
||||
```yaml
|
||||
network:
|
||||
ethernets:
|
||||
eno1:
|
||||
dhcp4: false
|
||||
addresses:
|
||||
- 192.168.68.100/22
|
||||
routes:
|
||||
- to: default
|
||||
via: 192.168.68.1
|
||||
nameservers:
|
||||
addresses:
|
||||
- 9.9.9.9
|
||||
- 1.1.1.1
|
||||
version: 2
|
||||
wifis:
|
||||
wlp1s0:
|
||||
access-points:
|
||||
This_Wifi_Sucks:
|
||||
password: "REDACTED_PASSWORD"
|
||||
dhcp4: true
|
||||
```
|
||||
|
||||
**Cloud-init is disabled** from managing network config:
|
||||
`/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg` — prevents reboots from reverting to DHCP.
|
||||
|
||||
> **Warning**: If you ever re-enable cloud-init networking or wipe this file, eno1 will revert to DHCP and AdGuard will start crash-looping on the next restart. See the Troubleshooting section below.
|
||||
|
||||
---
|
||||
|
||||
## Services
|
||||
|
||||
| Service | Port | URL |
|
||||
|---------|------|-----|
|
||||
| AdGuard Home (Web UI) | 9080 | http://192.168.68.100:9080 |
|
||||
| AdGuard Home (DNS) | 53 | 192.168.68.100:53, 100.72.55.21:53 |
|
||||
| Home Assistant | - | see homeassistant.yaml |
|
||||
| Plex | - | see plex.yaml |
|
||||
| Syncthing | - | see syncthing.yaml |
|
||||
| Invidious | 3000 | https://in.vish.gg (public), http://192.168.68.100:3000 |
|
||||
| Materialious | 3001 | http://192.168.68.100:3001 |
|
||||
| YourSpotify | 4000, 15000 | see yourspotify.yaml |
|
||||
|
||||
---
|
||||
|
||||
## Deployed Stacks
|
||||
|
||||
| Compose File | Service | Notes |
|
||||
|-------------|---------|-------|
|
||||
| `adguard.yaml` | AdGuard Home | DNS ad blocker, binds to 192.168.68.100 |
|
||||
| `homeassistant.yaml` | Home Assistant | Home automation |
|
||||
| `plex.yaml` | Plex | Media server |
|
||||
| `syncthing.yaml` | Syncthing | File sync |
|
||||
| `wireguard.yaml` | WireGuard / wg-easy | VPN |
|
||||
| `dyndns_updater.yaml` | DynDNS | Dynamic DNS |
|
||||
| `node-exporter.yaml` | Node Exporter | Prometheus metrics |
|
||||
| `piped.yaml` | Piped | YouTube alternative frontend |
|
||||
| `yourspotify.yaml` | YourSpotify | Spotify stats |
|
||||
| `invidious/invidious.yaml` | Invidious + Companion + DB + Materialious | YouTube frontend — https://in.vish.gg |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### AdGuard crash-loops on startup
|
||||
|
||||
**Symptom**: `docker ps` shows AdGuard as "Restarting" or "Up Less than a second"
|
||||
|
||||
**Cause**: AdGuard binds DNS to a specific IP (`192.168.68.100`). If the host's IP changes (DHCP), or if AdGuard rewrites its config to the current DHCP address, it will fail to bind on next start.
|
||||
|
||||
**Diagnose**:
|
||||
```bash
|
||||
docker logs AdGuard --tail 20
|
||||
# Look for: "bind: cannot assign requested address"
|
||||
# The log will show which IP it tried to use
|
||||
```
|
||||
|
||||
**Fix**:
|
||||
```bash
|
||||
# 1. Check what IP AdGuard thinks it should use
|
||||
sudo grep -A3 'bind_hosts' /home/vish/docker/adguard/config/AdGuardHome.yaml
|
||||
|
||||
# 2. Check what IP eno1 actually has
|
||||
ip addr show eno1 | grep 'inet '
|
||||
|
||||
# 3. If they don't match, update the config
|
||||
sudo sed -i 's/- 192.168.68.XXX/- 192.168.68.100/' /home/vish/docker/adguard/config/AdGuardHome.yaml
|
||||
|
||||
# 4. Restart AdGuard
|
||||
docker restart AdGuard
|
||||
```
|
||||
|
||||
**If the host IP has reverted to DHCP** (e.g. after a reboot wiped the static config):
|
||||
```bash
|
||||
# Re-apply static IP
|
||||
sudo netplan apply
|
||||
|
||||
# Verify
|
||||
ip addr show eno1 | grep 'inet '
|
||||
# Should show: inet 192.168.68.100/22
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Incident History
|
||||
|
||||
### 2026-02-22 — AdGuard crash-loop / IP mismatch
|
||||
|
||||
- **Root cause**: Host had drifted from `192.168.68.100` to DHCP-assigned `192.168.68.87`. AdGuard briefly started, rewrote its config to `.87`, then the static IP was applied and `.87` was gone — causing a bind failure loop.
|
||||
- **Resolution**:
|
||||
1. Disabled cloud-init network management
|
||||
2. Set `eno1` to static `192.168.68.100/22` via netplan
|
||||
3. Corrected `AdGuardHome.yaml` `bind_hosts` back to `.100`
|
||||
4. Restarted AdGuard — stable
|
||||
|
||||
---
|
||||
|
||||
### 2026-02-27 — Invidious 502 / crash-loop
|
||||
|
||||
- **Root cause 1**: PostgreSQL 14 defaults `pg_hba.conf` to `scram-sha-256` for host connections. Invidious's Crystal DB driver does not support scram-sha-256, causing a "password authentication failed" crash loop even with correct credentials.
|
||||
- **Fix**: Changed last line of `/var/lib/postgresql/data/pg_hba.conf` in the `invidious-db` container from `host all all all scram-sha-256` to `host all all 172.21.0.0/16 trust`, then ran `SELECT pg_reload_conf();`.
|
||||
- **Root cause 2**: Portainer had saved the literal string `REDACTED_SECRET_KEY` as the `SERVER_SECRET_KEY` env var for the companion container (Portainer's secret-redaction placeholder was baked in as the real value). The latest companion image validates the key strictly (exactly 16 alphanumeric chars), causing it to crash.
|
||||
- **Fix**: Updated the Portainer stack file via API (`PUT /api/stacks/584`), replacing all `REDACTED_*` placeholders with the real values.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-02-27*
|
||||
23
hosts/physical/concord-nuc/adguard.yaml
Normal file
23
hosts/physical/concord-nuc/adguard.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# AdGuard Home - DNS ad blocker
|
||||
# Web UI: http://192.168.68.100:9080
|
||||
# DNS: 192.168.68.100:53, 100.72.55.21:53
|
||||
#
|
||||
# IMPORTANT: This container binds DNS to 192.168.68.100 (configured in AdGuardHome.yaml).
|
||||
# The host MUST have a static IP of 192.168.68.100 on eno1, otherwise AdGuard will
|
||||
# crash-loop with "bind: cannot assign requested address".
|
||||
# See README.md for static IP setup and troubleshooting.
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome
|
||||
container_name: AdGuard
|
||||
mem_limit: 2g
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /home/vish/docker/adguard/config:/opt/adguardhome/conf:rw
|
||||
- /home/vish/docker/adguard/data:/opt/adguardhome/work:rw
|
||||
environment:
|
||||
TZ: America/Los_Angeles
|
||||
28
hosts/physical/concord-nuc/diun.yaml
Normal file
28
hosts/physical/concord-nuc/diun.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# Diun — Docker Image Update Notifier
|
||||
#
|
||||
# Watches all running containers on this host and sends ntfy
|
||||
# notifications when upstream images update their digest.
|
||||
# Schedule: Mondays 09:00 (weekly cadence).
|
||||
#
|
||||
# ntfy topic: https://ntfy.vish.gg/diun
|
||||
|
||||
services:
|
||||
diun:
|
||||
image: crazymax/diun:latest
|
||||
container_name: diun
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- diun-data:/data
|
||||
environment:
|
||||
LOG_LEVEL: info
|
||||
DIUN_WATCH_WORKERS: "20"
|
||||
DIUN_WATCH_SCHEDULE: "0 9 * * 1"
|
||||
DIUN_WATCH_JITTER: 30s
|
||||
DIUN_PROVIDERS_DOCKER: "true"
|
||||
DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT: "true"
|
||||
DIUN_NOTIF_NTFY_ENDPOINT: "https://ntfy.vish.gg"
|
||||
DIUN_NOTIF_NTFY_TOPIC: "diun"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
diun-data:
|
||||
@@ -0,0 +1,28 @@
|
||||
pds-g^KU_n-Ck6JOm^BQu9pcct0DI/MvsCnViM6kGHGVCigvohyf/HHHfHG8c=
|
||||
|
||||
|
||||
8. Start the Server
|
||||
Use screen or tmux to keep the server running in the background.
|
||||
|
||||
Start Master (Overworld) Server
|
||||
bash
|
||||
Copy
|
||||
Edit
|
||||
cd ~/dst/bin
|
||||
screen -S dst-master ./dontstarve_dedicated_server_nullrenderer -cluster MyCluster -shard Master
|
||||
Start Caves Server
|
||||
Open a new session:
|
||||
|
||||
bash
|
||||
Copy
|
||||
Edit
|
||||
|
||||
|
||||
screen -S dst-caves ./dontstarve_dedicated_server_nullrenderer -cluster MyCluster -shard Caves
|
||||
|
||||
|
||||
|
||||
[Service]
|
||||
User=dst
|
||||
ExecStart=/home/dstserver/dst/bin/dontstarve_dedicated_server_nullrenderer -cluster MyCluster -shard Master
|
||||
Restart=always
|
||||
15
hosts/physical/concord-nuc/dozzle-agent.yaml
Normal file
15
hosts/physical/concord-nuc/dozzle-agent.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
dozzle-agent:
|
||||
image: amir20/dozzle:latest
|
||||
container_name: dozzle-agent
|
||||
command: agent
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- "7007:7007"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "/dozzle", "healthcheck"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
17
hosts/physical/concord-nuc/dyndns_updater.yaml
Normal file
17
hosts/physical/concord-nuc/dyndns_updater.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Dynamic DNS Updater
|
||||
# Updates DNS records when public IP changes
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
ddns-vish-13340:
|
||||
image: favonia/cloudflare-ddns:latest
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
cap_drop: [all]
|
||||
security_opt: [no-new-privileges:true]
|
||||
environment:
|
||||
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
|
||||
- DOMAINS=api.vish.gg,api.vp.vish.gg,in.vish.gg
|
||||
- PROXIED=false
|
||||
55
hosts/physical/concord-nuc/homeassistant.yaml
Normal file
55
hosts/physical/concord-nuc/homeassistant.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
# Home Assistant - Smart home automation
|
||||
# Port: 8123
|
||||
# Open source home automation platform
|
||||
version: '3'
|
||||
services:
|
||||
homeassistant:
|
||||
container_name: homeassistant
|
||||
image: ghcr.io/home-assistant/home-assistant:stable
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=America/Los_Angeles
|
||||
volumes:
|
||||
- /home/vish/docker/homeassistant:/config
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
|
||||
matter-server:
|
||||
container_name: matter-server
|
||||
image: ghcr.io/home-assistant-libs/python-matter-server:stable
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /home/vish/docker/matter:/data
|
||||
|
||||
piper:
|
||||
container_name: piper
|
||||
image: rhasspy/wyoming-piper:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "10200:10200"
|
||||
volumes:
|
||||
- /home/vish/docker/piper:/data
|
||||
command: --voice en_US-lessac-medium
|
||||
|
||||
whisper:
|
||||
container_name: whisper
|
||||
image: rhasspy/wyoming-whisper:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "10300:10300"
|
||||
volumes:
|
||||
- /home/vish/docker/whisper:/data
|
||||
command: --model tiny-int8 --language en
|
||||
|
||||
openwakeword:
|
||||
container_name: openwakeword
|
||||
image: rhasspy/wyoming-openwakeword:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "10400:10400"
|
||||
command: --preload-model ok_nabu
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: homeassistant-stack
|
||||
34
hosts/physical/concord-nuc/homeassistant/CREDENTIALS.md
Normal file
34
hosts/physical/concord-nuc/homeassistant/CREDENTIALS.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Concord NUC Home Assistant — Credentials
|
||||
|
||||
Private repo — real secrets committed per repo policy.
|
||||
Public mirror (`homelab-optimized`) will substitute REDACTED_* placeholders.
|
||||
|
||||
## Oura Ring (OAuth2)
|
||||
|
||||
Application credentials created at [cloud.ouraring.com → Personal → API](https://cloud.ouraring.com/).
|
||||
Used by the **built-in** Home Assistant `oura` integration (HA 2024.4+, OAuth2 via application credentials).
|
||||
|
||||
- **Client ID**: `6dec0bb3-0739-4323-9a04-11ea8d05bdaa` <!-- pragma: allowlist secret -->
|
||||
- **Client Secret**: `gG___3Eeb4AYfUjpUyyqBqI3CZqFTjWOJ4-osIIqqYs` <!-- pragma: allowlist secret -->
|
||||
- **Redirect URI configured in Oura**: `https://my.home-assistant.io/redirect/oauth` (default HA flow)
|
||||
|
||||
### How to add to Home Assistant
|
||||
|
||||
1. Go to `Settings → Devices & Services → Helpers → Application Credentials → Add Credential`
|
||||
(or the integration setup flow will prompt for these on first run).
|
||||
2. Select **Oura** as the integration.
|
||||
3. Paste the Client ID and Client Secret above.
|
||||
4. Then `Settings → Devices & Services → Add Integration → Oura` → complete OAuth flow.
|
||||
|
||||
### Entities exposed (once integrated)
|
||||
|
||||
Expected entities created by the built-in integration:
|
||||
- `sensor.oura_readiness_score`
|
||||
- `sensor.oura_sleep_score`
|
||||
- `sensor.oura_activity_score`
|
||||
- `sensor.oura_resting_heart_rate`
|
||||
- `sensor.oura_heart_rate_variability`
|
||||
- `sensor.oura_total_sleep`
|
||||
- `sensor.oura_time_in_bed`
|
||||
|
||||
(Exact entity IDs may differ — verify after first integration run and update `dashboards/bedroom.yaml` accordingly.)
|
||||
115
hosts/physical/concord-nuc/homeassistant/FRIGATE_PLAN.md
Normal file
115
hosts/physical/concord-nuc/homeassistant/FRIGATE_PLAN.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Frigate NVR — Deployment Plan
|
||||
|
||||
Not yet deployed. The HACS `custom_components/frigate` integration is already on the HA instance, but no Frigate server exists to talk to.
|
||||
|
||||
## What Frigate brings
|
||||
- Object detection (person/car/dog/etc.) on camera streams, with far fewer false-positives than the Tapo built-in motion/person detection.
|
||||
- Timeline of detected events with snapshot + 10s clip per event.
|
||||
- RTSP relay + re-stream (can replace go2rtc in HA for this camera).
|
||||
- HA integration exposes: `binary_sensor.<camera>_person_occupancy`, `sensor.<camera>_person_count`, `image.<camera>_person_snapshot`, `camera.<camera>_person` image for the latest detection, and per-zone variants.
|
||||
|
||||
## Stack required on concord-nuc
|
||||
|
||||
```yaml
|
||||
# concord_nuc/frigate.yaml (new compose to add)
|
||||
services:
|
||||
frigate-mqtt:
|
||||
image: eclipse-mosquitto:2
|
||||
container_name: frigate-mqtt
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "1883:1883"
|
||||
volumes:
|
||||
- /home/vish/docker/mosquitto/config:/mosquitto/config
|
||||
- /home/vish/docker/mosquitto/data:/mosquitto/data
|
||||
|
||||
frigate:
|
||||
image: ghcr.io/blakeblackshear/frigate:stable
|
||||
container_name: frigate
|
||||
restart: unless-stopped
|
||||
privileged: true # for /dev/dri passthrough if using Intel QuickSync
|
||||
shm_size: 512mb
|
||||
ports:
|
||||
- "5000:5000" # web UI
|
||||
- "8554:8554" # RTSP relay
|
||||
- "8555:8555/tcp"
|
||||
- "8555:8555/udp" # WebRTC
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /home/vish/docker/frigate/config:/config
|
||||
- /home/vish/docker/frigate/media:/media/frigate
|
||||
- type: tmpfs
|
||||
target: /tmp/cache
|
||||
tmpfs:
|
||||
size: 1000000000
|
||||
devices:
|
||||
- /dev/dri/renderD128 # Intel iGPU (NUC6i3SYB has HD 520)
|
||||
depends_on:
|
||||
- frigate-mqtt
|
||||
```
|
||||
|
||||
## Frigate config (`/home/vish/docker/frigate/config/config.yml`)
|
||||
|
||||
```yaml
|
||||
mqtt:
|
||||
host: frigate-mqtt
|
||||
port: 1883
|
||||
|
||||
cameras:
|
||||
bedroom:
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://vishinator:<PASSWORD>@192.168.68.67:554/stream1
|
||||
roles: [detect, record]
|
||||
detect:
|
||||
width: 1920
|
||||
height: 1080
|
||||
fps: 5
|
||||
objects:
|
||||
track: [person, cat, dog]
|
||||
record:
|
||||
enabled: true
|
||||
retain:
|
||||
days: 7
|
||||
mode: motion
|
||||
snapshots:
|
||||
enabled: true
|
||||
retain:
|
||||
default: 30
|
||||
|
||||
# NUC6i3 has no NPU/GPU for ML — use CPU detector
|
||||
detectors:
|
||||
cpu1:
|
||||
type: cpu
|
||||
num_threads: 3
|
||||
```
|
||||
|
||||
## HA integration wiring
|
||||
|
||||
The `custom_components/frigate` integration config entry needs:
|
||||
- `url`: `http://localhost:5000` (Frigate UI on concord-nuc)
|
||||
- `password`: (if auth enabled)
|
||||
|
||||
Add via **Settings → Devices & Services → Add Integration → Frigate**.
|
||||
|
||||
## Dashboard changes (after deployment)
|
||||
|
||||
Update `dashboards/cameras.yaml` Bedroom section:
|
||||
- Replace `camera.vish_bedroom_camera_4k_hd_stream` with `camera.bedroom_frigate` (Frigate's re-stream)
|
||||
- Add `binary_sensor.bedroom_person_occupancy` to picture-glance entities
|
||||
- Add a "Recent Events" card: `custom:frigate-card` or a logbook filtered to Frigate sensors
|
||||
|
||||
## Storage considerations
|
||||
- 7-day retention with motion-mode recording on one 1080p/5fps stream ≈ 30-60 GB on the NUC's SSD (currently 73% full at 26 GB free).
|
||||
- Either allocate a larger disk, **change retain days to 2-3**, or mount `/media/frigate` to an NFS share on Atlantis (has ~TB free).
|
||||
|
||||
## Why deferred
|
||||
|
||||
1. Takes 1-2h to set up cleanly (MQTT configured, RTSP credentials tested, model tuned).
|
||||
2. NUC6i3 CPU is weak — detection on a single 1080p@5fps stream will probably max out the CPU. An Intel Coral TPU ($75) would fix this.
|
||||
3. The existing Tapo built-in motion/person detection already surfaces to HA via `tapo_control`; it's noisy but functional.
|
||||
|
||||
Pick this back up when:
|
||||
- You want real person-detection events + timeline
|
||||
- OR you add more cameras (Tapo's per-camera detection doesn't scale well)
|
||||
- OR you buy a Coral TPU
|
||||
77
hosts/physical/concord-nuc/homeassistant/configuration.yaml
Normal file
77
hosts/physical/concord-nuc/homeassistant/configuration.yaml
Normal file
@@ -0,0 +1,77 @@
|
||||
# Loads default set of integrations. Do not remove.
|
||||
default_config:
|
||||
|
||||
# Load frontend themes from the themes folder + inject Google Fonts
|
||||
frontend:
|
||||
themes: !include_dir_merge_named themes
|
||||
extra_module_url:
|
||||
- /local/fonts-loader.js
|
||||
- /local/assist-fix.js
|
||||
|
||||
# Legacy includes (kept for back-compat)
|
||||
automation: !include automations.yaml
|
||||
script: !include scripts.yaml
|
||||
scene: !include scenes.yaml
|
||||
|
||||
# REST sensors for homelab services (Sonarr, Radarr, Bazarr, SABnzbd, LazyLibrarian, ABS, Plex)
|
||||
sensor: !include sensors.yaml
|
||||
|
||||
# Custom YAML-mode dashboards (per-room + cameras)
|
||||
# Default "Overview" dashboard stays in storage mode.
|
||||
lovelace:
|
||||
mode: storage
|
||||
dashboards:
|
||||
home-view:
|
||||
mode: yaml
|
||||
title: Home
|
||||
icon: mdi:home
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/home.yaml
|
||||
living-room:
|
||||
mode: yaml
|
||||
title: Living Room
|
||||
icon: mdi:sofa
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/livingroom.yaml
|
||||
kitchen-view:
|
||||
mode: yaml
|
||||
title: Kitchen
|
||||
icon: mdi:stove
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/kitchen.yaml
|
||||
bathroom-view:
|
||||
mode: yaml
|
||||
title: Bathroom
|
||||
icon: mdi:shower
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/bathroom.yaml
|
||||
bedroom-view:
|
||||
mode: yaml
|
||||
title: Bedroom
|
||||
icon: mdi:bed
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/bedroom.yaml
|
||||
cameras-view:
|
||||
mode: yaml
|
||||
title: Cameras
|
||||
icon: mdi:cctv
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/cameras.yaml
|
||||
homelab-view:
|
||||
mode: yaml
|
||||
title: Homelab
|
||||
icon: mdi:server
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/homelab.yaml
|
||||
homelab-web:
|
||||
mode: yaml
|
||||
title: Homelab Web
|
||||
icon: mdi:home-analytics
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/homelab_web.yaml
|
||||
crista-web:
|
||||
mode: yaml
|
||||
title: Crista
|
||||
icon: mdi:heart
|
||||
show_in_sidebar: true
|
||||
filename: dashboards/crista.yaml
|
||||
@@ -0,0 +1,165 @@
|
||||
title: Bathroom
|
||||
views:
|
||||
- type: sections
|
||||
title: Bathroom
|
||||
path: bathroom
|
||||
icon: mdi:shower
|
||||
max_columns: 2
|
||||
sections:
|
||||
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:shower
|
||||
heading: Bathroom
|
||||
heading_style: title
|
||||
|
||||
- type: grid
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: tile
|
||||
entity: light.bathroom_light_1
|
||||
name: Light 1
|
||||
icon: mdi:lightbulb
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.bathroom_light_2
|
||||
name: Light 2
|
||||
icon: mdi:lightbulb
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.bathroom_light_3
|
||||
name: Light 3
|
||||
icon: mdi:lightbulb
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.bathroom_light_4
|
||||
name: Light 4
|
||||
icon: mdi:lightbulb
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.bathroom_light_5
|
||||
name: Light 5
|
||||
icon: mdi:lightbulb
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.bathroom_light_6
|
||||
name: Light 6
|
||||
icon: mdi:lightbulb
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
name: All On
|
||||
icon: mdi:lightbulb-group
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
target:
|
||||
entity_id:
|
||||
- light.bathroom_light_1
|
||||
- light.bathroom_light_2
|
||||
- light.bathroom_light_3
|
||||
- light.bathroom_light_4
|
||||
- light.bathroom_light_5
|
||||
- light.bathroom_light_6
|
||||
- type: button
|
||||
name: All Off
|
||||
icon: mdi:lightbulb-group-off
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_off
|
||||
target:
|
||||
entity_id:
|
||||
- light.bathroom_light_1
|
||||
- light.bathroom_light_2
|
||||
- light.bathroom_light_3
|
||||
- light.bathroom_light_4
|
||||
- light.bathroom_light_5
|
||||
- light.bathroom_light_6
|
||||
- type: button
|
||||
name: Relax
|
||||
icon: mdi:bathtub
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 35
|
||||
color_temp_kelvin: 2400
|
||||
target:
|
||||
entity_id:
|
||||
- light.bathroom_light_1
|
||||
- light.bathroom_light_2
|
||||
- light.bathroom_light_3
|
||||
- light.bathroom_light_4
|
||||
- light.bathroom_light_5
|
||||
- light.bathroom_light_6
|
||||
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:chart-line
|
||||
heading: Health
|
||||
heading_style: title
|
||||
|
||||
- type: entities
|
||||
title: Bulb Status
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: binary_sensor.bathroom_light_1_cloud_connection
|
||||
name: Light 1 - Cloud
|
||||
- entity: binary_sensor.bathroom_light_1_overheated
|
||||
name: Light 1 - Overheat
|
||||
- entity: binary_sensor.bathroom_light_2_cloud_connection
|
||||
name: Light 2 - Cloud
|
||||
- entity: binary_sensor.bathroom_light_2_overheated
|
||||
name: Light 2 - Overheat
|
||||
- entity: binary_sensor.bathroom_light_3_cloud_connection
|
||||
name: Light 3 - Cloud
|
||||
- entity: binary_sensor.bathroom_light_3_overheated
|
||||
name: Light 3 - Overheat
|
||||
- entity: binary_sensor.bathroom_light_4_cloud_connection
|
||||
name: Light 4 - Cloud
|
||||
- entity: binary_sensor.bathroom_light_4_overheated
|
||||
name: Light 4 - Overheat
|
||||
- entity: binary_sensor.bathroom_light_5_cloud_connection
|
||||
name: Light 5 - Cloud
|
||||
- entity: binary_sensor.bathroom_light_5_overheated
|
||||
name: Light 5 - Overheat
|
||||
- entity: binary_sensor.bathroom_light_6_cloud_connection
|
||||
name: Light 6 - Cloud
|
||||
- entity: binary_sensor.bathroom_light_6_overheated
|
||||
name: Light 6 - Overheat
|
||||
|
||||
- type: entities
|
||||
title: Signal Strength
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: sensor.bathroom_light_1_signal_level
|
||||
- entity: sensor.bathroom_light_2_signal_level
|
||||
- entity: sensor.bathroom_light_3_signal_level
|
||||
- entity: sensor.bathroom_light_4_signal_level
|
||||
- entity: sensor.bathroom_light_5_signal_level
|
||||
- entity: sensor.bathroom_light_6_signal_level
|
||||
|
||||
- type: markdown
|
||||
content: |
|
||||
_Room has no dedicated motion/humidity sensors.
|
||||
Consider adding a [Tapo T110](https://www.tapo.com) motion sensor
|
||||
or an Aqara Zigbee multi-sensor once the GL-S200 Thread BR arrives._
|
||||
386
hosts/physical/concord-nuc/homeassistant/dashboards/bedroom.yaml
Normal file
386
hosts/physical/concord-nuc/homeassistant/dashboards/bedroom.yaml
Normal file
@@ -0,0 +1,386 @@
|
||||
title: Bedroom
|
||||
views:
|
||||
- type: sections
|
||||
title: Bedroom
|
||||
path: bedroom
|
||||
icon: mdi:bed
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
# ---- Lights ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:lightbulb-multiple
|
||||
heading: Lights
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: light.vish_bedroom_light_1
|
||||
name: Light 1
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: light-color-temp
|
||||
|
||||
- type: tile
|
||||
entity: light.vish_bedroom_light_2
|
||||
name: Light 2
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: light-color-temp
|
||||
|
||||
- type: tile
|
||||
entity: light.vish_bedroom_light_3
|
||||
name: Light 3
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: light-color-temp
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
name: All On
|
||||
icon: mdi:lightbulb-group
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
target:
|
||||
entity_id:
|
||||
- light.vish_bedroom_light_1
|
||||
- light.vish_bedroom_light_2
|
||||
- light.vish_bedroom_light_3
|
||||
- type: button
|
||||
name: All Off
|
||||
icon: mdi:lightbulb-group-off
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_off
|
||||
target:
|
||||
entity_id:
|
||||
- light.vish_bedroom_light_1
|
||||
- light.vish_bedroom_light_2
|
||||
- light.vish_bedroom_light_3
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
name: Read
|
||||
icon: mdi:book-open-page-variant
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 80
|
||||
color_temp_kelvin: 3500
|
||||
target:
|
||||
entity_id:
|
||||
- light.vish_bedroom_light_1
|
||||
- light.vish_bedroom_light_2
|
||||
- light.vish_bedroom_light_3
|
||||
- type: button
|
||||
name: Sleep
|
||||
icon: mdi:weather-night
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 8
|
||||
color_temp_kelvin: 2200
|
||||
target:
|
||||
entity_id:
|
||||
- light.vish_bedroom_light_1
|
||||
- light.vish_bedroom_light_2
|
||||
- light.vish_bedroom_light_3
|
||||
|
||||
# ---- Camera ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:cctv
|
||||
heading: Bedroom Camera
|
||||
heading_style: title
|
||||
|
||||
- type: picture-glance
|
||||
title: Bedroom 4K
|
||||
camera_view: live
|
||||
camera_image: camera.vish_bedroom_camera_4k_hd_stream
|
||||
entities:
|
||||
- binary_sensor.vish_bedroom_camera_4k_motion_alarm
|
||||
- binary_sensor.vish_bedroom_camera_4k_person_detection
|
||||
- switch.vish_bedroom_camera_4k_privacy
|
||||
- light.vish_bedroom_camera_4k_floodlight_timed
|
||||
- siren.vish_bedroom_camera_4k_siren
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
name: Up
|
||||
icon: mdi:arrow-up-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_tilt_up
|
||||
- type: button
|
||||
name: Down
|
||||
icon: mdi:arrow-down-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_tilt_down
|
||||
- type: button
|
||||
name: Left
|
||||
icon: mdi:arrow-left-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_pan_left
|
||||
- type: button
|
||||
name: Right
|
||||
icon: mdi:arrow-right-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_pan_right
|
||||
|
||||
- type: heading
|
||||
icon: mdi:cctv-off
|
||||
heading: Detection
|
||||
heading_style: subtitle
|
||||
|
||||
- type: grid
|
||||
columns: 2
|
||||
square: false
|
||||
cards:
|
||||
- type: tile
|
||||
entity: switch.vish_bedroom_camera_4k_motion_detection
|
||||
name: Motion
|
||||
icon: mdi:motion-sensor
|
||||
vertical: false
|
||||
tap_action:
|
||||
action: toggle
|
||||
- type: tile
|
||||
entity: switch.vish_bedroom_camera_4k_person_detection
|
||||
name: Person
|
||||
icon: mdi:account-search
|
||||
vertical: false
|
||||
tap_action:
|
||||
action: toggle
|
||||
- type: tile
|
||||
entity: switch.vish_bedroom_camera_4k_privacy
|
||||
name: Privacy
|
||||
icon: mdi:eye-off
|
||||
vertical: false
|
||||
tap_action:
|
||||
action: toggle
|
||||
- type: tile
|
||||
entity: select.vish_bedroom_camera_4k_night_vision
|
||||
name: Night Vision
|
||||
icon: mdi:weather-night
|
||||
vertical: false
|
||||
tap_action:
|
||||
action: more-info
|
||||
|
||||
# ---- Media ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:cast
|
||||
heading: Media
|
||||
heading_style: title
|
||||
|
||||
- type: media-control
|
||||
entity: media_player.tv_bedroom
|
||||
- type: media-control
|
||||
entity: media_player.bedroom_display
|
||||
- type: media-control
|
||||
entity: media_player.spotify_vish_khemraj
|
||||
|
||||
# ---- Sleep / Oura ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:ring
|
||||
heading: Oura Ring
|
||||
heading_style: title
|
||||
|
||||
# Hero scores
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.oura_ring_sleep_score
|
||||
name: Sleep
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 85
|
||||
yellow: 70
|
||||
red: 0
|
||||
needle: true
|
||||
- type: gauge
|
||||
entity: sensor.oura_ring_readiness_score
|
||||
name: Readiness
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 85
|
||||
yellow: 70
|
||||
red: 0
|
||||
needle: true
|
||||
- type: gauge
|
||||
entity: sensor.oura_ring_activity_score
|
||||
name: Activity
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 85
|
||||
yellow: 70
|
||||
red: 0
|
||||
needle: true
|
||||
- type: gauge
|
||||
entity: sensor.oura_ring_stress_resilience_score
|
||||
name: Resilience
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 70
|
||||
yellow: 50
|
||||
red: 0
|
||||
needle: true
|
||||
|
||||
# Sleep breakdown
|
||||
- type: entities
|
||||
title: Last Night
|
||||
show_header_toggle: false
|
||||
state_color: true
|
||||
entities:
|
||||
- entity: sensor.oura_ring_total_sleep_duration
|
||||
name: Total Sleep
|
||||
icon: mdi:bed-clock
|
||||
- entity: sensor.oura_ring_deep_sleep_duration
|
||||
name: Deep
|
||||
icon: mdi:water
|
||||
- entity: sensor.oura_ring_rem_sleep_duration
|
||||
name: REM
|
||||
icon: mdi:eye
|
||||
- entity: sensor.oura_ring_light_sleep_duration
|
||||
name: Light
|
||||
icon: mdi:weather-sunset
|
||||
- entity: sensor.oura_ring_awake_time
|
||||
name: Awake
|
||||
icon: mdi:eye-outline
|
||||
- entity: sensor.oura_ring_sleep_efficiency
|
||||
name: Efficiency
|
||||
icon: mdi:percent
|
||||
- entity: sensor.oura_ring_bedtime_start
|
||||
name: Bedtime
|
||||
icon: mdi:clock-start
|
||||
- entity: sensor.oura_ring_bedtime_end
|
||||
name: Wake
|
||||
icon: mdi:clock-end
|
||||
|
||||
# Trends
|
||||
- type: history-graph
|
||||
title: Sleep & Readiness (14d)
|
||||
hours_to_show: 336
|
||||
entities:
|
||||
- entity: sensor.oura_ring_sleep_score
|
||||
- entity: sensor.oura_ring_readiness_score
|
||||
|
||||
- type: history-graph
|
||||
title: HRV & Resting HR (14d)
|
||||
hours_to_show: 336
|
||||
entities:
|
||||
- entity: sensor.oura_ring_average_sleep_hrv
|
||||
- entity: sensor.oura_ring_lowest_sleep_heart_rate
|
||||
|
||||
# Vitals + activity
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.oura_ring_average_sleep_hrv
|
||||
name: Avg HRV
|
||||
icon: mdi:heart-pulse
|
||||
- type: tile
|
||||
entity: sensor.oura_ring_lowest_sleep_heart_rate
|
||||
name: Low HR
|
||||
icon: mdi:heart
|
||||
- type: tile
|
||||
entity: sensor.oura_ring_temperature_deviation
|
||||
name: Temp Δ
|
||||
icon: mdi:thermometer
|
||||
- type: tile
|
||||
entity: sensor.oura_ring_vo2_max
|
||||
name: VO₂ Max
|
||||
icon: mdi:lungs
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.oura_ring_steps
|
||||
name: Steps
|
||||
icon: mdi:shoe-print
|
||||
- type: tile
|
||||
entity: sensor.oura_ring_active_calories
|
||||
name: Active Cal
|
||||
icon: mdi:fire
|
||||
- type: tile
|
||||
entity: sensor.oura_ring_cardiovascular_age
|
||||
name: CV Age
|
||||
icon: mdi:heart-cog
|
||||
- type: tile
|
||||
entity: binary_sensor.oura_ring_rest_mode
|
||||
name: Rest Mode
|
||||
icon: mdi:sleep
|
||||
|
||||
- type: conditional
|
||||
conditions:
|
||||
- condition: state
|
||||
entity: sensor.oura_ring_low_battery_alert
|
||||
state: "on"
|
||||
card:
|
||||
type: tile
|
||||
entity: sensor.oura_ring_low_battery_alert
|
||||
name: Ring Battery Low
|
||||
icon: mdi:battery-alert
|
||||
|
||||
# ---- Power / Environment ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:flash
|
||||
heading: Guava Power
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: switch.guava_energy
|
||||
name: Guava (TrueNAS) Plug
|
||||
icon: mdi:server-network
|
||||
vertical: true
|
||||
tap_action:
|
||||
action: toggle
|
||||
|
||||
- type: entities
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: sensor.guava_energy_current_consumption
|
||||
name: Power Now
|
||||
- entity: sensor.guava_energy_today_s_consumption
|
||||
name: Today
|
||||
- entity: sensor.guava_energy_this_month_s_consumption
|
||||
name: This Month
|
||||
- entity: sensor.guava_energy_voltage
|
||||
name: Voltage
|
||||
- entity: sensor.guava_energy_current
|
||||
name: Current
|
||||
165
hosts/physical/concord-nuc/homeassistant/dashboards/cameras.yaml
Normal file
165
hosts/physical/concord-nuc/homeassistant/dashboards/cameras.yaml
Normal file
@@ -0,0 +1,165 @@
|
||||
title: Cameras
|
||||
views:
|
||||
- type: sections
|
||||
title: Live
|
||||
path: live
|
||||
icon: mdi:cctv
|
||||
max_columns: 2
|
||||
sections:
|
||||
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:bed
|
||||
heading: Bedroom 4K
|
||||
heading_style: title
|
||||
|
||||
- type: picture-glance
|
||||
title: Bedroom - HD
|
||||
camera_view: live
|
||||
camera_image: camera.vish_bedroom_camera_4k_hd_stream
|
||||
entities:
|
||||
- binary_sensor.vish_bedroom_camera_4k_motion_alarm
|
||||
- binary_sensor.vish_bedroom_camera_4k_person_detection
|
||||
- switch.vish_bedroom_camera_4k_privacy
|
||||
- light.vish_bedroom_camera_4k_floodlight_timed
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
icon: mdi:arrow-up-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_tilt_up
|
||||
- type: button
|
||||
icon: mdi:arrow-down-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_tilt_down
|
||||
- type: button
|
||||
icon: mdi:arrow-left-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_pan_left
|
||||
- type: button
|
||||
icon: mdi:arrow-right-bold
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.vish_bedroom_camera_4k_pan_right
|
||||
|
||||
- type: heading
|
||||
icon: mdi:cctv-off
|
||||
heading: Detection Settings
|
||||
heading_style: subtitle
|
||||
|
||||
- type: grid
|
||||
columns: 2
|
||||
square: false
|
||||
cards:
|
||||
- type: tile
|
||||
entity: switch.vish_bedroom_camera_4k_motion_detection
|
||||
name: Motion
|
||||
icon: mdi:motion-sensor
|
||||
tap_action:
|
||||
action: toggle
|
||||
- type: tile
|
||||
entity: switch.vish_bedroom_camera_4k_person_detection
|
||||
name: Person
|
||||
icon: mdi:account-search
|
||||
tap_action:
|
||||
action: toggle
|
||||
- type: tile
|
||||
entity: switch.vish_bedroom_camera_4k_privacy
|
||||
name: Privacy
|
||||
icon: mdi:eye-off
|
||||
tap_action:
|
||||
action: toggle
|
||||
- type: tile
|
||||
entity: select.vish_bedroom_camera_4k_night_vision
|
||||
name: Night Vision
|
||||
icon: mdi:weather-night
|
||||
tap_action:
|
||||
action: more-info
|
||||
- type: tile
|
||||
entity: select.vish_bedroom_camera_4k_patrol_mode
|
||||
name: Patrol
|
||||
icon: mdi:shield-search
|
||||
tap_action:
|
||||
action: more-info
|
||||
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:sofa
|
||||
heading: Living Room
|
||||
heading_style: title
|
||||
|
||||
- type: picture-glance
|
||||
title: Living Room
|
||||
camera_view: live
|
||||
camera_image: camera.192_168_69_116
|
||||
entities: []
|
||||
|
||||
- type: heading
|
||||
icon: mdi:home-outline
|
||||
heading: Other Cameras
|
||||
heading_style: subtitle
|
||||
|
||||
- type: picture-glance
|
||||
title: Camera (192.168.68.67)
|
||||
camera_view: live
|
||||
camera_image: camera.192_168_68_67
|
||||
entities: []
|
||||
|
||||
- type: heading
|
||||
icon: mdi:cctv
|
||||
heading: Setillo (Estudio)
|
||||
heading_style: subtitle
|
||||
|
||||
- type: picture-glance
|
||||
title: Estudio (Surveillance Station)
|
||||
camera_view: live
|
||||
camera_image: camera.estudio
|
||||
entities:
|
||||
- switch.setillo_surveillance_station_home_mode
|
||||
|
||||
- type: conditional
|
||||
conditions:
|
||||
- condition: state
|
||||
entity: camera.192_168_12_155
|
||||
state_not: unavailable
|
||||
card:
|
||||
type: picture-glance
|
||||
title: Hawaii Camera
|
||||
camera_view: live
|
||||
camera_image: camera.192_168_12_155
|
||||
entities: []
|
||||
|
||||
- type: sections
|
||||
title: Events
|
||||
path: events
|
||||
icon: mdi:motion-sensor
|
||||
sections:
|
||||
- type: grid
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:motion-sensor
|
||||
heading: Recent Motion
|
||||
heading_style: title
|
||||
|
||||
- type: logbook
|
||||
hours_to_show: 24
|
||||
entities:
|
||||
- binary_sensor.vish_bedroom_camera_4k_motion_alarm
|
||||
- binary_sensor.vish_bedroom_camera_4k_person_detection
|
||||
- binary_sensor.vish_bedroom_camera_4k_cell_motion_detection
|
||||
@@ -0,0 +1,10 @@
|
||||
title: Crista
|
||||
views:
|
||||
- title: crista.love
|
||||
path: crista
|
||||
type: panel
|
||||
icon: mdi:heart
|
||||
cards:
|
||||
- type: iframe
|
||||
url: https://crista.love
|
||||
aspect_ratio: "100%"
|
||||
361
hosts/physical/concord-nuc/homeassistant/dashboards/home.yaml
Normal file
361
hosts/physical/concord-nuc/homeassistant/dashboards/home.yaml
Normal file
@@ -0,0 +1,361 @@
|
||||
title: Home
|
||||
views:
|
||||
- type: sections
|
||||
title: Home
|
||||
path: home
|
||||
icon: mdi:home
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
# ---- Greeting + presence ----
|
||||
- type: grid
|
||||
column_span: 3
|
||||
cards:
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{% set t = now().hour %}
|
||||
{% if t < 5 %}Good night, Vish
|
||||
{% elif t < 12 %}Good morning, Vish
|
||||
{% elif t < 17 %}Good afternoon, Vish
|
||||
{% elif t < 21 %}Good evening, Vish
|
||||
{% else %}Good night, Vish{% endif %}
|
||||
secondary: >-
|
||||
{{ as_timestamp(now()) | timestamp_custom('%A, %B %-d • %-I:%M %p') }}
|
||||
icon: mdi:home-heart
|
||||
icon_color: >-
|
||||
{% set t = now().hour %}
|
||||
{% if t < 6 or t > 20 %}indigo
|
||||
{% elif t < 10 %}amber
|
||||
{% elif t < 17 %}blue
|
||||
{% else %}deep-orange{% endif %}
|
||||
tap_action:
|
||||
action: none
|
||||
|
||||
- type: custom:mushroom-chips-card
|
||||
alignment: center
|
||||
chips:
|
||||
- type: entity
|
||||
entity: weather.forecast_home
|
||||
icon_color: blue
|
||||
tap_action:
|
||||
action: more-info
|
||||
- type: entity
|
||||
entity: sensor.oura_ring_readiness_score
|
||||
name: Readiness
|
||||
icon: mdi:ring
|
||||
icon_color: green
|
||||
content_info: state
|
||||
tap_action:
|
||||
action: more-info
|
||||
- type: entity
|
||||
entity: sensor.oura_ring_sleep_score
|
||||
name: Sleep
|
||||
icon: mdi:sleep
|
||||
icon_color: indigo
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.atlantis
|
||||
name: Plex
|
||||
icon: mdi:plex
|
||||
icon_color: orange
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.adguard_home_dns_queries_blocked_ratio
|
||||
name: AdGuard
|
||||
icon: mdi:shield-check
|
||||
icon_color: teal
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.speedtest_download
|
||||
icon: mdi:download-network
|
||||
icon_color: cyan
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.atlantis_cpu_utilization_total
|
||||
name: NAS CPU
|
||||
icon: mdi:nas
|
||||
icon_color: blue
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.pve_cpu_usage
|
||||
name: PVE CPU
|
||||
icon: mdi:server-network
|
||||
icon_color: green
|
||||
content_info: state
|
||||
|
||||
# ---- Persons ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:account-multiple
|
||||
heading: People
|
||||
heading_style: title
|
||||
|
||||
- type: custom:mushroom-person-card
|
||||
entity: person.vish
|
||||
layout: horizontal
|
||||
primary_info: name
|
||||
secondary_info: state
|
||||
icon_type: entity-picture
|
||||
- type: custom:mushroom-person-card
|
||||
entity: person.crista
|
||||
layout: horizontal
|
||||
primary_info: name
|
||||
secondary_info: state
|
||||
icon_type: entity-picture
|
||||
|
||||
# ---- Weather hero ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:weather-partly-cloudy
|
||||
heading: Weather
|
||||
heading_style: title
|
||||
|
||||
- type: weather-forecast
|
||||
entity: weather.forecast_home
|
||||
forecast_type: daily
|
||||
|
||||
# ---- Oura hero ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:ring
|
||||
heading: Oura Today
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.oura_ring_readiness_score
|
||||
name: Ready
|
||||
min: 0
|
||||
max: 100
|
||||
needle: true
|
||||
severity:
|
||||
green: 85
|
||||
yellow: 70
|
||||
red: 0
|
||||
- type: gauge
|
||||
entity: sensor.oura_ring_sleep_score
|
||||
name: Sleep
|
||||
min: 0
|
||||
max: 100
|
||||
needle: true
|
||||
severity:
|
||||
green: 85
|
||||
yellow: 70
|
||||
red: 0
|
||||
|
||||
- type: custom:mushroom-template-card
|
||||
primary: "Slept {{ states('sensor.oura_ring_total_sleep_duration') }} hrs"
|
||||
secondary: >-
|
||||
HRV {{ states('sensor.oura_ring_average_sleep_hrv') }} •
|
||||
HR {{ states('sensor.oura_ring_lowest_sleep_heart_rate') }}
|
||||
icon: mdi:bed-clock
|
||||
icon_color: indigo
|
||||
|
||||
# ---- Rooms nav ----
|
||||
- type: grid
|
||||
column_span: 3
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:floor-plan
|
||||
heading: Rooms
|
||||
heading_style: title
|
||||
|
||||
- type: grid
|
||||
columns: 5
|
||||
square: false
|
||||
cards:
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Living Room
|
||||
secondary: >-
|
||||
{% set lights = [] %}
|
||||
{% if states('media_player.tv_living_room') != 'off' %}TV on{% else %}{{ states('sensor.speedtest_download') }} Mbps ↓{% endif %}
|
||||
icon: mdi:sofa
|
||||
icon_color: >-
|
||||
{% if states('media_player.tv_living_room') not in ['off','standby','unavailable'] %}deep-orange{% else %}grey{% endif %}
|
||||
tap_action:
|
||||
action: navigate
|
||||
navigation_path: /living-room/living-room
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Kitchen
|
||||
secondary: >-
|
||||
{% set lights = ['light.kitchen_above_sink','light.kitchen_light_1','light.kitchen_light_2','light.kitchen_light_3','light.kitchen_light_4'] %}
|
||||
{% set on = lights | select('is_state','on') | list | count %}
|
||||
{{ on }}/{{ lights|count }} lights on
|
||||
icon: mdi:stove
|
||||
icon_color: >-
|
||||
{% set lights = ['light.kitchen_above_sink','light.kitchen_light_1','light.kitchen_light_2','light.kitchen_light_3','light.kitchen_light_4'] %}
|
||||
{% set on = lights | select('is_state','on') | list | count %}
|
||||
{% if on > 0 %}amber{% else %}grey{% endif %}
|
||||
tap_action:
|
||||
action: navigate
|
||||
navigation_path: /kitchen-view/kitchen
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Bathroom
|
||||
secondary: >-
|
||||
{% set lights = ['light.bathroom_light_1','light.bathroom_light_2','light.bathroom_light_3','light.bathroom_light_4','light.bathroom_light_5','light.bathroom_light_6'] %}
|
||||
{% set on = lights | select('is_state','on') | list | count %}
|
||||
{{ on }}/{{ lights|count }} lights on
|
||||
icon: mdi:shower
|
||||
icon_color: >-
|
||||
{% set lights = ['light.bathroom_light_1','light.bathroom_light_2','light.bathroom_light_3','light.bathroom_light_4','light.bathroom_light_5','light.bathroom_light_6'] %}
|
||||
{% set on = lights | select('is_state','on') | list | count %}
|
||||
{% if on > 0 %}amber{% else %}grey{% endif %}
|
||||
tap_action:
|
||||
action: navigate
|
||||
navigation_path: /bathroom-view/bathroom
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Bedroom
|
||||
secondary: >-
|
||||
{% set lights = ['light.vish_bedroom_light_1','light.vish_bedroom_light_2','light.vish_bedroom_light_3'] %}
|
||||
{% set on = lights | select('is_state','on') | list | count %}
|
||||
{{ on }}/{{ lights|count }} • {{ states('switch.guava_energy') }} plug
|
||||
icon: mdi:bed
|
||||
icon_color: >-
|
||||
{% set lights = ['light.vish_bedroom_light_1','light.vish_bedroom_light_2','light.vish_bedroom_light_3'] %}
|
||||
{% set on = lights | select('is_state','on') | list | count %}
|
||||
{% if on > 0 %}amber{% else %}purple{% endif %}
|
||||
tap_action:
|
||||
action: navigate
|
||||
navigation_path: /bedroom-view/bedroom
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Cameras
|
||||
secondary: >-
|
||||
{% if is_state('binary_sensor.vish_bedroom_camera_4k_motion_alarm','on') %}⚠ Motion
|
||||
{% elif is_state('switch.vish_bedroom_camera_4k_privacy','on') %}Privacy on
|
||||
{% else %}Armed{% endif %}
|
||||
icon: mdi:cctv
|
||||
icon_color: >-
|
||||
{% if is_state('binary_sensor.vish_bedroom_camera_4k_motion_alarm','on') %}red
|
||||
{% elif is_state('switch.vish_bedroom_camera_4k_privacy','on') %}grey
|
||||
{% else %}teal{% endif %}
|
||||
tap_action:
|
||||
action: navigate
|
||||
navigation_path: /cameras-view/live
|
||||
|
||||
# ---- Quick actions / scenes ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:flash
|
||||
heading: Quick Actions
|
||||
heading_style: title
|
||||
|
||||
- type: grid
|
||||
columns: 4
|
||||
square: true
|
||||
cards:
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Goodnight
|
||||
icon: mdi:moon-waxing-crescent
|
||||
icon_color: indigo
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_off
|
||||
data: {}
|
||||
target:
|
||||
entity_id:
|
||||
- light.kitchen_above_sink
|
||||
- light.kitchen_light_1
|
||||
- light.kitchen_light_2
|
||||
- light.kitchen_light_3
|
||||
- light.kitchen_light_4
|
||||
- light.bathroom_light_1
|
||||
- light.bathroom_light_2
|
||||
- light.bathroom_light_3
|
||||
- light.bathroom_light_4
|
||||
- light.bathroom_light_5
|
||||
- light.bathroom_light_6
|
||||
- type: custom:mushroom-template-card
|
||||
primary: All Lights Off
|
||||
icon: mdi:lightbulb-off
|
||||
icon_color: grey
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_off
|
||||
data:
|
||||
entity_id: all
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Bedtime Dim
|
||||
icon: mdi:bed
|
||||
icon_color: purple
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 10
|
||||
color_temp_kelvin: 2200
|
||||
target:
|
||||
entity_id:
|
||||
- light.vish_bedroom_light_1
|
||||
- light.vish_bedroom_light_2
|
||||
- light.vish_bedroom_light_3
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Movie Mode
|
||||
icon: mdi:movie-open
|
||||
icon_color: deep-orange
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 15
|
||||
color_temp_kelvin: 2500
|
||||
target:
|
||||
entity_id:
|
||||
- light.kitchen_above_sink
|
||||
- light.kitchen_light_1
|
||||
- light.kitchen_light_2
|
||||
- light.kitchen_light_3
|
||||
- light.kitchen_light_4
|
||||
|
||||
# ---- Homelab strip ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:server
|
||||
heading: Homelab
|
||||
heading_style: title
|
||||
|
||||
- type: custom:mushroom-chips-card
|
||||
alignment: start
|
||||
chips:
|
||||
- type: entity
|
||||
entity: sensor.sonarr_wanted
|
||||
icon: mdi:television-classic
|
||||
icon_color: blue
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.radarr_missing
|
||||
icon: mdi:filmstrip
|
||||
icon_color: orange
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.sabnzbd_speed
|
||||
icon: mdi:download
|
||||
icon_color: cyan
|
||||
content_info: state
|
||||
- type: entity
|
||||
entity: sensor.bazarr_badges
|
||||
icon: mdi:subtitles
|
||||
icon_color: purple
|
||||
content_info: state
|
||||
|
||||
- type: custom:mushroom-template-card
|
||||
primary: Library Totals
|
||||
secondary: >-
|
||||
{{ states('sensor.sonarr_shows') }} series •
|
||||
{{ states('sensor.radarr_movies_2') }} movies •
|
||||
{{ states('sensor.audiobookshelf_ebooks') }} ebooks
|
||||
icon: mdi:library-shelves
|
||||
icon_color: green
|
||||
tap_action:
|
||||
action: navigate
|
||||
navigation_path: /homelab-view/homelab
|
||||
811
hosts/physical/concord-nuc/homeassistant/dashboards/homelab.yaml
Normal file
811
hosts/physical/concord-nuc/homeassistant/dashboards/homelab.yaml
Normal file
@@ -0,0 +1,811 @@
|
||||
title: Homelab
|
||||
views:
|
||||
|
||||
# ========================================================
|
||||
# TAB 1: OVERVIEW — calendar + kuma summary + quick launch
|
||||
# ========================================================
|
||||
- type: sections
|
||||
title: Overview
|
||||
path: homelab
|
||||
icon: mdi:view-dashboard
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
# ---- Calendar (Baikal + Radarr) ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:calendar-month
|
||||
heading: Calendar
|
||||
heading_style: title
|
||||
|
||||
- type: custom:calendar-card-pro
|
||||
entities:
|
||||
- entity: calendar.vish
|
||||
color: "#a78bfa"
|
||||
- entity: calendar.radarr
|
||||
color: "#f59e0b"
|
||||
days_to_show: 14
|
||||
max_events_to_show: 20
|
||||
show_past_events: false
|
||||
compact_mode: false
|
||||
|
||||
# ---- Kuma summary ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:heart-pulse
|
||||
heading: Uptime Kuma
|
||||
heading_style: title
|
||||
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{{ states.sensor | selectattr('entity_id','match','^sensor\..*_status$') | list | count }}
|
||||
secondary: Total Monitors
|
||||
icon: mdi:pulse
|
||||
icon_color: blue
|
||||
tap_action:
|
||||
action: url
|
||||
url_path: http://100.77.151.40:3001
|
||||
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{{ states.sensor | selectattr('entity_id','match','^sensor\..*_status$')
|
||||
| rejectattr('state','eq','up')
|
||||
| rejectattr('state','eq','unavailable')
|
||||
| rejectattr('state','eq','unknown')
|
||||
| list | count }}
|
||||
secondary: Down / Degraded
|
||||
icon: mdi:alert-circle
|
||||
icon_color: red
|
||||
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{% set rts = states.sensor | selectattr('entity_id','match','^sensor\..*_response_time$')
|
||||
| map(attribute='state') | reject('in',['unavailable','unknown','none'])
|
||||
| map('float',0) | reject('le',0) | list %}
|
||||
{{ (rts | sum / (rts | count | max(1)) ) | round(0) }}
|
||||
secondary: Avg Response (ms)
|
||||
icon: mdi:speedometer
|
||||
icon_color: green
|
||||
|
||||
# ---- Library totals ----
|
||||
- type: grid
|
||||
column_span: 3
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:library
|
||||
heading: Library Totals
|
||||
heading_style: title
|
||||
|
||||
- type: grid
|
||||
columns: 5
|
||||
square: false
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_movies
|
||||
name: Movies
|
||||
icon: mdi:movie-open
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_tv_shows
|
||||
name: TV Shows
|
||||
icon: mdi:television-classic
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_anime
|
||||
name: Anime
|
||||
icon: mdi:sword-cross
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_music
|
||||
name: Music
|
||||
icon: mdi:music
|
||||
- type: tile
|
||||
entity: sensor.audiobookshelf_ebooks
|
||||
name: Ebooks
|
||||
icon: mdi:book-open-variant
|
||||
|
||||
# ---- Quick launch ----
|
||||
- type: grid
|
||||
column_span: 3
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:rocket-launch
|
||||
heading: Quick Launch
|
||||
heading_style: title
|
||||
|
||||
- type: grid
|
||||
columns: 6
|
||||
square: true
|
||||
cards:
|
||||
- type: button
|
||||
name: Plex
|
||||
icon: mdi:plex
|
||||
tap_action: {action: url, url_path: https://app.plex.tv}
|
||||
- type: button
|
||||
name: Sonarr
|
||||
icon: mdi:television-classic
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:8989}
|
||||
- type: button
|
||||
name: Radarr
|
||||
icon: mdi:filmstrip
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:7878}
|
||||
- type: button
|
||||
name: Bazarr
|
||||
icon: mdi:subtitles
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:6767}
|
||||
- type: button
|
||||
name: Prowlarr
|
||||
icon: mdi:magnify
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:9696}
|
||||
- type: button
|
||||
name: SABnzbd
|
||||
icon: mdi:download
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:8080}
|
||||
- type: button
|
||||
name: LazyLib
|
||||
icon: mdi:book-clock
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:5299}
|
||||
- type: button
|
||||
name: ABS
|
||||
icon: mdi:headphones
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:13378}
|
||||
- type: button
|
||||
name: Portainer
|
||||
icon: mdi:docker
|
||||
tap_action: {action: url, url_path: https://pt.vish.gg}
|
||||
- type: button
|
||||
name: Gitea
|
||||
icon: mdi:git
|
||||
tap_action: {action: url, url_path: https://git.vish.gg}
|
||||
- type: button
|
||||
name: Homarr
|
||||
icon: mdi:view-dashboard
|
||||
tap_action: {action: url, url_path: https://homarr.vish.gg}
|
||||
- type: button
|
||||
name: Kuma
|
||||
icon: mdi:heart-pulse
|
||||
tap_action: {action: url, url_path: http://100.77.151.40:3001}
|
||||
|
||||
# ========================================================
|
||||
# TAB 2: MEDIA — Plex, *arr, downloads, books
|
||||
# ========================================================
|
||||
- type: sections
|
||||
title: Media
|
||||
path: media
|
||||
icon: mdi:play-circle
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
# ---- Plex ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:plex
|
||||
heading: Plex
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: sensor.atlantis
|
||||
name: Now Playing
|
||||
icon: mdi:plex
|
||||
tap_action:
|
||||
action: more-info
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_movies
|
||||
name: Movies
|
||||
icon: mdi:movie-open
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_tv_shows
|
||||
name: TV
|
||||
icon: mdi:television-classic
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_anime
|
||||
name: Anime
|
||||
icon: mdi:sword-cross
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_music
|
||||
name: Music
|
||||
icon: mdi:music
|
||||
|
||||
# ---- Sonarr + Radarr ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:television-classic
|
||||
heading: Sonarr + Radarr
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.sonarr_queue_2
|
||||
name: Sonarr Q
|
||||
icon: mdi:television-classic
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:8989}
|
||||
- type: tile
|
||||
entity: sensor.radarr_queue_2
|
||||
name: Radarr Q
|
||||
icon: mdi:filmstrip
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:7878}
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.sonarr_shows
|
||||
name: Shows
|
||||
icon: mdi:television-box
|
||||
- type: tile
|
||||
entity: sensor.radarr_movies_2
|
||||
name: Movies
|
||||
icon: mdi:movie-open
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.sonarr_upcoming
|
||||
name: Upcoming
|
||||
icon: mdi:calendar-clock
|
||||
- type: tile
|
||||
entity: sensor.sonarr_wanted
|
||||
name: Wanted
|
||||
icon: mdi:television-off
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.bazarr_badges
|
||||
name: Bazarr
|
||||
icon: mdi:subtitles
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:6767}
|
||||
- type: tile
|
||||
entity: sensor.prowlarr_indexers
|
||||
name: Prowlarr
|
||||
icon: mdi:magnify
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:9696}
|
||||
|
||||
# ---- Books ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:book-open-page-variant
|
||||
heading: Books & Audiobooks
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: sensor.audiobookshelf_ebooks
|
||||
name: Ebooks
|
||||
icon: mdi:book-open-variant
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:13378}
|
||||
|
||||
- type: tile
|
||||
entity: sensor.audiobookshelf_audiobooks
|
||||
name: Audiobooks
|
||||
icon: mdi:headphones
|
||||
|
||||
- type: tile
|
||||
entity: sensor.lazylibrarian_wanted_books
|
||||
name: LazyLib Wanted
|
||||
icon: mdi:book-clock
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:5299}
|
||||
|
||||
- type: tile
|
||||
entity: sensor.lazylibrarian_version
|
||||
name: LazyLib Version
|
||||
icon: mdi:tag
|
||||
|
||||
# ---- Downloads (SABnzbd) ----
|
||||
- type: grid
|
||||
column_span: 3
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:download
|
||||
heading: Downloads — SABnzbd
|
||||
heading_style: title
|
||||
|
||||
- type: grid
|
||||
columns: 3
|
||||
square: false
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.sabnzbd_speed
|
||||
name: Speed
|
||||
icon: mdi:download-network
|
||||
tap_action: {action: url, url_path: http://100.83.230.112:8080}
|
||||
- type: tile
|
||||
entity: sensor.sabnzbd_queue
|
||||
name: Queue
|
||||
icon: mdi:tray-full
|
||||
- type: tile
|
||||
entity: sensor.sabnzbd_status
|
||||
name: Status
|
||||
icon: mdi:information-outline
|
||||
|
||||
- type: entities
|
||||
title: Details
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: sensor.sabnzbd_left_to_download
|
||||
- entity: sensor.sabnzbd_queue_count
|
||||
- entity: sensor.sabnzbd_free_disk_space
|
||||
- entity: sensor.sabnzbd_daily_total
|
||||
- entity: sensor.sabnzbd_weekly_total
|
||||
- entity: sensor.sabnzbd_monthly_total
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
entity: button.sabnzbd_pause
|
||||
name: Pause
|
||||
icon: mdi:pause
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.sabnzbd_pause
|
||||
- type: button
|
||||
entity: button.sabnzbd_resume
|
||||
name: Resume
|
||||
icon: mdi:play
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.sabnzbd_resume
|
||||
|
||||
# ========================================================
|
||||
# TAB 3: SERVERS — NASes + Proxmox + TrueNAS
|
||||
# ========================================================
|
||||
- type: sections
|
||||
title: Servers
|
||||
path: servers
|
||||
icon: mdi:server-network
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
# ---- Atlantis NAS (Synology) ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:nas
|
||||
heading: Atlantis NAS (Synology)
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.atlantis_cpu_utilization_total
|
||||
name: CPU
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 60, red: 85}
|
||||
- type: gauge
|
||||
entity: sensor.atlantis_memory_usage_real
|
||||
name: Memory
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 70, red: 90}
|
||||
- type: gauge
|
||||
entity: sensor.atlantis_temperature
|
||||
name: Temp °F
|
||||
min: 60
|
||||
max: 180
|
||||
severity: {green: 60, yellow: 140, red: 160}
|
||||
|
||||
- type: entities
|
||||
title: Volumes
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.atlantis_volume_1_volume_used, name: Volume 1}
|
||||
- {entity: sensor.atlantis_volume_2_volume_used, name: Volume 2}
|
||||
- {entity: sensor.atlantis_volume_3_volume_used, name: Volume 3}
|
||||
- {entity: sensor.atlantis_volume_1_used_space, name: V1 Used (TB)}
|
||||
- {entity: sensor.atlantis_volume_1_status, name: V1 Health}
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.atlantis_download_throughput
|
||||
name: ↓ Download
|
||||
icon: mdi:download
|
||||
- type: tile
|
||||
entity: sensor.atlantis_upload_throughput
|
||||
name: ↑ Upload
|
||||
icon: mdi:upload
|
||||
|
||||
- type: entities
|
||||
title: Drive Temperatures (°F)
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.atlantis_drive_1_temperature, name: Drive 1}
|
||||
- {entity: sensor.atlantis_drive_2_temperature, name: Drive 2}
|
||||
- {entity: sensor.atlantis_drive_3_temperature, name: Drive 3}
|
||||
- {entity: sensor.atlantis_drive_4_temperature, name: Drive 4}
|
||||
- {entity: sensor.atlantis_drive_5_temperature, name: Drive 5}
|
||||
- {entity: sensor.atlantis_drive_6_temperature, name: Drive 6}
|
||||
- {entity: sensor.atlantis_drive_7_temperature, name: Drive 7}
|
||||
- {entity: sensor.atlantis_drive_8_temperature, name: Drive 8}
|
||||
- {entity: sensor.atlantis_m_2_drive_1_temperature, name: NVMe 1}
|
||||
- {entity: sensor.atlantis_m_2_drive_2_temperature, name: NVMe 2}
|
||||
|
||||
- type: conditional
|
||||
conditions:
|
||||
- condition: state
|
||||
entity: update.atlantis_dsm_update
|
||||
state: "on"
|
||||
card:
|
||||
type: tile
|
||||
entity: update.atlantis_dsm_update
|
||||
name: ⚠ DSM Update Available
|
||||
|
||||
# ---- Calypso NAS (compact) ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:nas
|
||||
heading: Calypso NAS
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.calypso_cpu_utilization_total
|
||||
name: CPU
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 60, red: 85}
|
||||
- type: gauge
|
||||
entity: sensor.calypso_memory_usage_real
|
||||
name: Mem
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 70, red: 90}
|
||||
|
||||
- type: entities
|
||||
title: Volume & Drives
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.calypso_volume_1_volume_used, name: Volume 1 %}
|
||||
- {entity: sensor.calypso_volume_1_used_space, name: V1 Used (TB)}
|
||||
- {entity: sensor.calypso_volume_1_status, name: V1 Health}
|
||||
- {entity: sensor.calypso_drive_1_temperature, name: Drive 1 Temp}
|
||||
- {entity: sensor.calypso_drive_2_temperature, name: Drive 2 Temp}
|
||||
- {entity: sensor.calypso_m_2_drive_1_temperature, name: NVMe 1 Temp}
|
||||
- {entity: sensor.calypso_m_2_drive_2_temperature, name: NVMe 2 Temp}
|
||||
- {entity: sensor.calypso_temperature, name: System Temp}
|
||||
- {entity: binary_sensor.calypso_security_status, name: Security}
|
||||
|
||||
- type: conditional
|
||||
conditions:
|
||||
- condition: state
|
||||
entity: update.calypso_dsm_update
|
||||
state: "on"
|
||||
card:
|
||||
type: tile
|
||||
entity: update.calypso_dsm_update
|
||||
name: ⚠ Calypso DSM Update
|
||||
|
||||
# ---- Setillo NAS (compact) ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:nas
|
||||
heading: Setillo NAS
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.setillo_cpu_utilization_total
|
||||
name: CPU
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 60, red: 85}
|
||||
- type: gauge
|
||||
entity: sensor.setillo_memory_usage_real
|
||||
name: Mem
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 70, red: 90}
|
||||
|
||||
- type: entities
|
||||
title: Volume & Drives
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.setillo_volume_1_volume_used, name: Volume 1 %}
|
||||
- {entity: sensor.setillo_volume_1_used_space, name: V1 Used (TB)}
|
||||
- {entity: sensor.setillo_volume_1_status, name: V1 Health}
|
||||
- {entity: sensor.setillo_drive_1_temperature, name: Drive 1 Temp}
|
||||
- {entity: sensor.setillo_drive_2_temperature, name: Drive 2 Temp}
|
||||
- {entity: sensor.setillo_temperature, name: System Temp}
|
||||
- {entity: binary_sensor.setillo_security_status, name: Security}
|
||||
- {entity: switch.setillo_surveillance_station_home_mode, name: Surveillance Home Mode}
|
||||
|
||||
- type: conditional
|
||||
conditions:
|
||||
- condition: state
|
||||
entity: update.setillo_dsm_update
|
||||
state: "on"
|
||||
card:
|
||||
type: tile
|
||||
entity: update.setillo_dsm_update
|
||||
name: ⚠ Setillo DSM Update
|
||||
|
||||
# ---- Guava TrueNAS ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:harddisk
|
||||
heading: Guava (TrueNAS Scale)
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.guava_system_cpu_usage
|
||||
name: CPU
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 60, red: 85}
|
||||
- type: gauge
|
||||
entity: sensor.guava_system_memory_usage
|
||||
name: Memory
|
||||
min: 0
|
||||
max: 100
|
||||
unit: "%"
|
||||
severity: {green: 0, yellow: 70, red: 90}
|
||||
- type: tile
|
||||
entity: sensor.guava_system_temperature
|
||||
name: Temp
|
||||
icon: mdi:thermometer
|
||||
|
||||
- type: entities
|
||||
title: Pool Health
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: binary_sensor.guava_system_data_healthy, name: Data Pool}
|
||||
- {entity: binary_sensor.guava_system_boot_pool_healthy, name: Boot Pool}
|
||||
- {entity: sensor.guava_system_data_free, name: Data Pool Free}
|
||||
- {entity: sensor.guava_system_boot_pool_free, name: Boot Pool Free}
|
||||
- {entity: sensor.guava_system_arc_size, name: ZFS ARC Size}
|
||||
- {entity: sensor.guava_system_uptime, name: Uptime}
|
||||
|
||||
- type: entities
|
||||
title: Services + VM
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: binary_sensor.guava_services_nfs, name: NFS}
|
||||
- {entity: binary_sensor.guava_services_cifs, name: SMB/CIFS}
|
||||
- {entity: binary_sensor.guava_services_ssh, name: SSH}
|
||||
- {entity: binary_sensor.guava_services_smartd, name: SMART Daemon}
|
||||
- {entity: binary_sensor.guava_services_snmp, name: SNMP}
|
||||
- {entity: binary_sensor.guava_services_ups, name: UPS}
|
||||
- {entity: binary_sensor.guava_vms_proton_bridge, name: Proton Bridge VM}
|
||||
|
||||
- type: entities
|
||||
title: Disks (SMART)
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.guava_disks_nvme0n1, name: NVMe (boot)}
|
||||
- {entity: sensor.guava_disks_sda, name: Disk sda}
|
||||
- {entity: sensor.guava_disks_sdb, name: Disk sdb}
|
||||
|
||||
- type: conditional
|
||||
conditions:
|
||||
- condition: state
|
||||
entity: update.guava_system
|
||||
state: "on"
|
||||
card:
|
||||
type: tile
|
||||
entity: update.guava_system
|
||||
name: ⚠ Guava TrueNAS Update
|
||||
|
||||
# ---- Proxmox VE ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:server-network
|
||||
heading: Proxmox VE
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.pve_cpu_usage
|
||||
name: CPU
|
||||
min: 0
|
||||
max: 100
|
||||
severity: {green: 0, yellow: 60, red: 85}
|
||||
- type: gauge
|
||||
entity: sensor.pve_memory_usage_percentage
|
||||
name: Mem
|
||||
min: 0
|
||||
max: 100
|
||||
severity: {green: 0, yellow: 70, red: 90}
|
||||
|
||||
- type: entities
|
||||
title: PVE Host
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: binary_sensor.pve_status, name: Status}
|
||||
- {entity: sensor.pve_uptime, name: Uptime}
|
||||
- {entity: sensor.pve_memory_usage, name: Memory Used}
|
||||
- {entity: sensor.pve_disk_usage, name: Disk Used}
|
||||
- {entity: binary_sensor.pve_backup_status, name: Backup Status}
|
||||
- {entity: sensor.pve_last_backup, name: Last Backup}
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
entity: button.pve_start_all
|
||||
name: Start All
|
||||
icon: mdi:play-circle
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.pve_start_all
|
||||
- type: button
|
||||
entity: button.pve_stop_all
|
||||
name: Stop All
|
||||
icon: mdi:stop-circle
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.pve_stop_all
|
||||
- type: button
|
||||
entity: button.pve_restart
|
||||
name: Restart
|
||||
icon: mdi:restart
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: button.press
|
||||
target:
|
||||
entity_id: button.pve_restart
|
||||
confirmation:
|
||||
text: "Restart PVE host?"
|
||||
|
||||
# ========================================================
|
||||
# TAB 4: MONITORING — Kuma detail + service pings
|
||||
# ========================================================
|
||||
- type: sections
|
||||
title: Monitoring
|
||||
path: monitoring
|
||||
icon: mdi:heart-pulse
|
||||
max_columns: 2
|
||||
sections:
|
||||
|
||||
# ---- Summary chips (same as Overview) ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:heart-pulse
|
||||
heading: Uptime Kuma — Live
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{{ states.sensor | selectattr('entity_id','match','^sensor\..*_status$') | list | count }}
|
||||
secondary: Total Monitors
|
||||
icon: mdi:pulse
|
||||
icon_color: blue
|
||||
tap_action:
|
||||
action: url
|
||||
url_path: http://100.77.151.40:3001
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{{ states.sensor | selectattr('entity_id','match','^sensor\..*_status$')
|
||||
| rejectattr('state','eq','up')
|
||||
| rejectattr('state','eq','unavailable')
|
||||
| rejectattr('state','eq','unknown')
|
||||
| list | count }}
|
||||
secondary: Down
|
||||
icon: mdi:alert-circle
|
||||
icon_color: red
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{% set rts = states.sensor | selectattr('entity_id','match','^sensor\..*_response_time$')
|
||||
| map(attribute='state') | reject('in',['unavailable','unknown','none'])
|
||||
| map('float',0) | reject('le',0) | list %}
|
||||
{{ (rts | sum / (rts | count | max(1)) ) | round(0) }}
|
||||
secondary: Avg Response (ms)
|
||||
icon: mdi:speedometer
|
||||
icon_color: green
|
||||
- type: custom:mushroom-template-card
|
||||
primary: >-
|
||||
{{ states.sensor | selectattr('entity_id','match','^sensor\..*_certificate_expiry$')
|
||||
| map(attribute='state') | map('int',999) | reject('ge',30) | list | count }}
|
||||
secondary: Certs < 30 days
|
||||
icon: mdi:certificate
|
||||
icon_color: orange
|
||||
|
||||
# ---- Core infra ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:server
|
||||
heading: Core Infrastructure
|
||||
heading_style: subtitle
|
||||
|
||||
- type: entities
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.atlantis_status, name: Atlantis (NAS)}
|
||||
- {entity: sensor.proxmox_nuc_status, name: Proxmox NUC}
|
||||
- {entity: sensor.home_assistant_status, name: Home Assistant}
|
||||
- {entity: sensor.authentik_status, name: Authentik SSO}
|
||||
- {entity: sensor.headscale_status, name: Headscale}
|
||||
- {entity: sensor.crowdsec_lapi_status, name: CrowdSec}
|
||||
- {entity: sensor.nginx_proxy_manager_status, name: NPM}
|
||||
- {entity: sensor.atl_portainer_status, name: Portainer}
|
||||
|
||||
# ---- Apps ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:application
|
||||
heading: Apps & Services
|
||||
heading_style: subtitle
|
||||
|
||||
- type: entities
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.jellyfin_status, name: Jellyfin}
|
||||
- {entity: sensor.ollama_status, name: Ollama}
|
||||
- {entity: sensor.gitea_status, name: Gitea}
|
||||
- {entity: sensor.grafana_status, name: Grafana}
|
||||
- {entity: sensor.homarr_status, name: Homarr}
|
||||
- {entity: sensor.bitwarden_status, name: Bitwarden}
|
||||
- {entity: sensor.paperless_ngx_status, name: Paperless}
|
||||
- {entity: sensor.immich_status, name: Immich}
|
||||
- {entity: sensor.seafile_status, name: Seafile}
|
||||
|
||||
# ---- Certificate expiry watch ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:certificate
|
||||
heading: SSL Certificates (sorted by expiry)
|
||||
heading_style: subtitle
|
||||
|
||||
- type: entities
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- {entity: sensor.crista_s_website_certificate_expiry, name: crista.love}
|
||||
- {entity: sensor.gitea_certificate_expiry, name: gitea}
|
||||
- {entity: sensor.homarr_certificate_expiry, name: homarr}
|
||||
- {entity: sensor.authentik_certificate_expiry, name: authentik}
|
||||
- {entity: sensor.headscale_certificate_expiry, name: headscale}
|
||||
- {entity: sensor.ollama_certificate_expiry, name: ollama}
|
||||
- {entity: sensor.grafana_certificate_expiry, name: grafana}
|
||||
- {entity: sensor.nginx_proxy_manager_certificate_expiry, name: npm}
|
||||
- {entity: sensor.crowdsec_lapi_certificate_expiry, name: crowdsec}
|
||||
- {entity: sensor.matrix_certificate_expiry, name: matrix}
|
||||
@@ -0,0 +1,10 @@
|
||||
title: Homelab Web
|
||||
views:
|
||||
- title: Dashboard
|
||||
path: web
|
||||
type: panel
|
||||
icon: mdi:home-analytics
|
||||
cards:
|
||||
- type: iframe
|
||||
url: http://homelab.tail.vish.gg:3100
|
||||
aspect_ratio: "100%"
|
||||
161
hosts/physical/concord-nuc/homeassistant/dashboards/kitchen.yaml
Normal file
161
hosts/physical/concord-nuc/homeassistant/dashboards/kitchen.yaml
Normal file
@@ -0,0 +1,161 @@
|
||||
title: Kitchen
|
||||
views:
|
||||
- type: sections
|
||||
title: Kitchen
|
||||
path: kitchen
|
||||
icon: mdi:stove
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:stove
|
||||
heading: Kitchen
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: light.kitchen_above_sink
|
||||
name: Above Sink
|
||||
icon: mdi:ceiling-light
|
||||
vertical: true
|
||||
features_position: bottom
|
||||
features:
|
||||
- type: light-brightness
|
||||
tap_action:
|
||||
action: toggle
|
||||
|
||||
- type: grid
|
||||
columns: 2
|
||||
square: false
|
||||
cards:
|
||||
- type: tile
|
||||
entity: light.kitchen_light_1
|
||||
name: Light 1
|
||||
icon: mdi:ceiling-light
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.kitchen_light_2
|
||||
name: Light 2
|
||||
icon: mdi:ceiling-light
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.kitchen_light_3
|
||||
name: Light 3
|
||||
icon: mdi:ceiling-light
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
- type: tile
|
||||
entity: light.kitchen_light_4
|
||||
name: Light 4
|
||||
icon: mdi:ceiling-light
|
||||
vertical: true
|
||||
features:
|
||||
- type: light-brightness
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
name: All On
|
||||
icon: mdi:lightbulb-group
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
target:
|
||||
entity_id:
|
||||
- light.kitchen_above_sink
|
||||
- light.kitchen_light_1
|
||||
- light.kitchen_light_2
|
||||
- light.kitchen_light_3
|
||||
- light.kitchen_light_4
|
||||
- type: button
|
||||
name: All Off
|
||||
icon: mdi:lightbulb-group-off
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_off
|
||||
target:
|
||||
entity_id:
|
||||
- light.kitchen_above_sink
|
||||
- light.kitchen_light_1
|
||||
- light.kitchen_light_2
|
||||
- light.kitchen_light_3
|
||||
- light.kitchen_light_4
|
||||
- type: button
|
||||
name: Cooking
|
||||
icon: mdi:chef-hat
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 100
|
||||
color_temp_kelvin: 4000
|
||||
target:
|
||||
entity_id:
|
||||
- light.kitchen_above_sink
|
||||
- light.kitchen_light_1
|
||||
- light.kitchen_light_2
|
||||
- light.kitchen_light_3
|
||||
- light.kitchen_light_4
|
||||
- type: button
|
||||
name: Dim
|
||||
icon: mdi:weather-night
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 20
|
||||
color_temp_kelvin: 2700
|
||||
target:
|
||||
entity_id:
|
||||
- light.kitchen_above_sink
|
||||
- light.kitchen_light_1
|
||||
- light.kitchen_light_2
|
||||
- light.kitchen_light_3
|
||||
- light.kitchen_light_4
|
||||
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:chart-line
|
||||
heading: Health
|
||||
heading_style: title
|
||||
|
||||
- type: entities
|
||||
title: Bulb Status
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: binary_sensor.kitchen_above_sink_cloud_connection
|
||||
name: Above Sink - Cloud
|
||||
- entity: binary_sensor.kitchen_above_sink_overheated
|
||||
name: Above Sink - Overheat
|
||||
- entity: binary_sensor.kitchen_light_1_cloud_connection
|
||||
name: Light 1 - Cloud
|
||||
- entity: binary_sensor.kitchen_light_2_cloud_connection
|
||||
name: Light 2 - Cloud
|
||||
- entity: binary_sensor.kitchen_light_3_cloud_connection
|
||||
name: Light 3 - Cloud
|
||||
- entity: binary_sensor.kitchen_light_4_cloud_connection
|
||||
name: Light 4 - Cloud
|
||||
|
||||
- type: entities
|
||||
title: Signal Strength
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: sensor.kitchen_above_sink_signal_level
|
||||
name: Above Sink
|
||||
- entity: sensor.kitchen_light_1_signal_level
|
||||
name: Light 1
|
||||
- entity: sensor.kitchen_light_2_signal_level
|
||||
name: Light 2
|
||||
- entity: sensor.kitchen_light_3_signal_level
|
||||
name: Light 3
|
||||
- entity: sensor.kitchen_light_4_signal_level
|
||||
name: Light 4
|
||||
@@ -0,0 +1,227 @@
|
||||
title: Living Room
|
||||
views:
|
||||
- type: sections
|
||||
title: Living Room
|
||||
path: living-room
|
||||
icon: mdi:sofa
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
# ---- Weather ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:weather-partly-cloudy
|
||||
heading: Weather
|
||||
heading_style: title
|
||||
|
||||
- type: weather-forecast
|
||||
entity: weather.forecast_home
|
||||
forecast_type: daily
|
||||
show_current: true
|
||||
show_forecast: true
|
||||
|
||||
- type: weather-forecast
|
||||
entity: weather.forecast_home
|
||||
forecast_type: hourly
|
||||
show_current: false
|
||||
show_forecast: true
|
||||
|
||||
# ---- Media ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:television
|
||||
heading: Media
|
||||
heading_style: title
|
||||
|
||||
- type: media-control
|
||||
entity: media_player.tv_living_room
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: button
|
||||
icon: mdi:spotify
|
||||
name: Spotify
|
||||
tap_action:
|
||||
action: navigate
|
||||
navigation_path: /bedroom-yaml/bedroom
|
||||
- type: button
|
||||
icon: mdi:cast
|
||||
name: Cast Hub
|
||||
tap_action:
|
||||
action: more-info
|
||||
entity: media_player.tv_living_room
|
||||
- type: button
|
||||
icon: mdi:remote-tv
|
||||
name: TV Power
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: media_player.toggle
|
||||
target:
|
||||
entity_id: media_player.tv_living_room
|
||||
|
||||
- type: picture-glance
|
||||
title: Living Room Camera
|
||||
camera_view: live
|
||||
camera_image: camera.192_168_69_116
|
||||
entities: []
|
||||
|
||||
# ---- Security / Hub ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:security
|
||||
heading: Hub & Alarm
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: siren.hub_wired
|
||||
name: Hub Siren
|
||||
icon: mdi:alarm-light
|
||||
vertical: true
|
||||
tap_action:
|
||||
action: toggle
|
||||
|
||||
- type: entities
|
||||
title: Hub
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: switch.hub_wired_led
|
||||
name: Hub LED
|
||||
- entity: select.hub_wired_alarm_sound
|
||||
name: Alarm Sound
|
||||
|
||||
# ---- Network health ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:wifi
|
||||
heading: Network
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: gauge
|
||||
entity: sensor.speedtest_download
|
||||
name: Download
|
||||
unit: Mbps
|
||||
min: 0
|
||||
max: 1000
|
||||
severity:
|
||||
green: 300
|
||||
yellow: 100
|
||||
red: 0
|
||||
- type: gauge
|
||||
entity: sensor.speedtest_upload
|
||||
name: Upload
|
||||
unit: Mbps
|
||||
min: 0
|
||||
max: 500
|
||||
severity:
|
||||
green: 100
|
||||
yellow: 30
|
||||
red: 0
|
||||
- type: gauge
|
||||
entity: sensor.speedtest_ping
|
||||
name: Ping
|
||||
unit: ms
|
||||
min: 0
|
||||
max: 100
|
||||
severity:
|
||||
green: 0
|
||||
yellow: 30
|
||||
red: 60
|
||||
needle: true
|
||||
|
||||
- type: history-graph
|
||||
title: Download / Upload (24h)
|
||||
hours_to_show: 24
|
||||
entities:
|
||||
- entity: sensor.speedtest_download
|
||||
- entity: sensor.speedtest_upload
|
||||
|
||||
- type: entities
|
||||
title: Deco Mesh
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: binary_sensor.living_room_deco_internet_online
|
||||
name: Living Room Deco - Internet
|
||||
- entity: binary_sensor.living_room_deco_deco_online
|
||||
name: Living Room Deco - Online
|
||||
- entity: binary_sensor.main_bedroom_deco_deco_online
|
||||
name: Main Bedroom Deco
|
||||
- entity: binary_sensor.kevins_room_deco_deco_online
|
||||
name: Kevin's Room Deco
|
||||
|
||||
# ---- Electricity (PG&E) ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:flash
|
||||
heading: Electricity (PG&E)
|
||||
heading_style: title
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.current_bill_electric_usage_to_date
|
||||
name: Usage So Far
|
||||
icon: mdi:meter-electric
|
||||
- type: tile
|
||||
entity: sensor.current_bill_electric_forecasted_usage
|
||||
name: Forecasted Usage
|
||||
icon: mdi:chart-line-variant
|
||||
- type: tile
|
||||
entity: sensor.typical_monthly_electric_usage
|
||||
name: Typical
|
||||
icon: mdi:calendar-month
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.current_bill_electric_cost_to_date
|
||||
name: Cost So Far
|
||||
icon: mdi:currency-usd
|
||||
- type: tile
|
||||
entity: sensor.current_bill_electric_forecasted_cost
|
||||
name: Forecast
|
||||
icon: mdi:cash-multiple
|
||||
- type: tile
|
||||
entity: sensor.typical_monthly_electric_cost
|
||||
name: Typical Cost
|
||||
icon: mdi:cash-clock
|
||||
|
||||
# ---- AdGuard ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:shield-check
|
||||
heading: AdGuard
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: switch.adguard_home_protection
|
||||
name: Protection
|
||||
icon: mdi:shield
|
||||
|
||||
- type: entities
|
||||
title: Today
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: sensor.adguard_home_dns_queries
|
||||
name: DNS Queries
|
||||
- entity: sensor.adguard_home_dns_queries_blocked
|
||||
name: Blocked
|
||||
- entity: sensor.adguard_home_dns_queries_blocked_ratio
|
||||
name: Block Ratio
|
||||
- entity: sensor.adguard_home_safe_browsing_blocked
|
||||
name: Safe Browsing
|
||||
- entity: sensor.adguard_home_rules_count
|
||||
name: Rules
|
||||
14
hosts/physical/concord-nuc/homeassistant/secrets.yaml
Normal file
14
hosts/physical/concord-nuc/homeassistant/secrets.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Home Assistant secrets — referenced as !secret <key> in configuration
|
||||
# Private repo: committed directly (REDACTED in public mirror).
|
||||
|
||||
# Homelab arr-suite (Atlantis, Tailscale 100.83.230.112)
|
||||
sonarr_api_key: "REDACTED_API_KEY"
|
||||
radarr_api_key: "REDACTED_API_KEY"
|
||||
sabnzbd_api_key: "REDACTED_API_KEY"
|
||||
prowlarr_api_key: "REDACTED_API_KEY"
|
||||
bazarr_api_key: "REDACTED_API_KEY"
|
||||
lazylibrarian_api_key: "REDACTED_LL_API_KEY"
|
||||
audiobookshelf_api_key: "Bearer REDACTED_ABS_API_TOKEN"
|
||||
|
||||
# Plex (local on this NUC)
|
||||
plex_token: "REDACTED_TOKEN"
|
||||
82
hosts/physical/concord-nuc/homeassistant/sensors.yaml
Normal file
82
hosts/physical/concord-nuc/homeassistant/sensors.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
# REST sensors for homelab services that don't have native HA integrations.
|
||||
# Sonarr / Radarr / SABnzbd / Plex now use native HA integrations instead —
|
||||
# their REST sensor definitions were removed 2026-04-19.
|
||||
#
|
||||
# Service ports (Atlantis via Tailscale 100.83.230.112):
|
||||
# bazarr 6767 /api
|
||||
# lazylib 5299 /api
|
||||
# abs 13378 /api
|
||||
# prowlarr 9696 /api/v1
|
||||
|
||||
# -------- Prowlarr --------
|
||||
- platform: rest
|
||||
name: Prowlarr Indexers
|
||||
unique_id: homelab_rest_prowlarr_indexers
|
||||
resource: "http://100.83.230.112:9696/api/v1/indexer"
|
||||
headers:
|
||||
X-Api-Key: !secret prowlarr_api_key
|
||||
value_template: "{{ value_json | length }}"
|
||||
unit_of_measurement: indexers
|
||||
scan_interval: 3600
|
||||
|
||||
# -------- Bazarr --------
|
||||
- platform: rest
|
||||
name: Bazarr Badges
|
||||
unique_id: homelab_rest_bazarr_badges
|
||||
resource: "http://100.83.230.112:6767/api/badges"
|
||||
headers:
|
||||
X-Api-Key: !secret bazarr_api_key
|
||||
value_template: "{{ (value_json.missing_subtitles_movies | default(0)) + (value_json.missing_subtitles_episodes | default(0)) }}"
|
||||
unit_of_measurement: missing
|
||||
json_attributes:
|
||||
- missing_subtitles_episodes
|
||||
- missing_subtitles_movies
|
||||
- throttled_providers
|
||||
scan_interval: 600
|
||||
|
||||
# -------- LazyLibrarian --------
|
||||
- platform: rest
|
||||
name: LazyLibrarian Version
|
||||
unique_id: homelab_rest_lazylibrarian_version
|
||||
resource: "http://100.83.230.112:5299/api?apikey=REDACTED_LL_API_KEY&cmd=getVersion"
|
||||
value_template: "{{ value_json.current_version | default('?') }}"
|
||||
scan_interval: 86400
|
||||
|
||||
- platform: rest
|
||||
name: LazyLibrarian Wanted Books
|
||||
unique_id: homelab_rest_lazylibrarian_wanted_books
|
||||
resource: "http://100.83.230.112:5299/api?apikey=REDACTED_LL_API_KEY&cmd=getWanted"
|
||||
value_template: "{{ value_json.data | length if value_json.data is defined else 0 }}"
|
||||
unit_of_measurement: books
|
||||
scan_interval: 600
|
||||
|
||||
# -------- Audiobookshelf --------
|
||||
- platform: rest
|
||||
name: Audiobookshelf Libraries
|
||||
unique_id: homelab_rest_audiobookshelf_libraries
|
||||
resource: "http://100.83.230.112:13378/api/libraries"
|
||||
headers:
|
||||
Authorization: !secret audiobookshelf_api_key
|
||||
value_template: "{{ value_json.libraries | length }}"
|
||||
unit_of_measurement: libraries
|
||||
scan_interval: 3600
|
||||
|
||||
- platform: rest
|
||||
name: Audiobookshelf Ebooks
|
||||
unique_id: homelab_rest_audiobookshelf_ebooks
|
||||
resource: "http://100.83.230.112:13378/api/libraries/5af23ed3-f69d-479b-88bc-1c4911c99d2d/items?limit=1"
|
||||
headers:
|
||||
Authorization: !secret audiobookshelf_api_key
|
||||
value_template: "{{ value_json.total | default(0) }}"
|
||||
unit_of_measurement: items
|
||||
scan_interval: 3600
|
||||
|
||||
- platform: rest
|
||||
name: Audiobookshelf Audiobooks
|
||||
unique_id: homelab_rest_audiobookshelf_audiobooks
|
||||
resource: "http://100.83.230.112:13378/api/libraries/d36776eb-fe81-467f-8fee-19435ee2827b/items?limit=1"
|
||||
headers:
|
||||
Authorization: !secret audiobookshelf_api_key
|
||||
value_template: "{{ value_json.total | default(0) }}"
|
||||
unit_of_measurement: items
|
||||
scan_interval: 3600
|
||||
143
hosts/physical/concord-nuc/homeassistant/themes/cyberpunk.yaml
Normal file
143
hosts/physical/concord-nuc/homeassistant/themes/cyberpunk.yaml
Normal file
@@ -0,0 +1,143 @@
|
||||
cyberpunk:
|
||||
# ===== font (angular, tech) =====
|
||||
primary-font-family: '"Rajdhani", "Orbitron", "Share Tech Mono", "Exo 2", -apple-system, sans-serif'
|
||||
paper-font-common-base_-_font-family: '"Rajdhani", "Orbitron", "Exo 2", -apple-system, sans-serif'
|
||||
paper-font-body1_-_font-family: '"Rajdhani", "Exo 2", -apple-system, sans-serif'
|
||||
paper-font-subhead_-_font-family: '"Rajdhani", "Exo 2", -apple-system, sans-serif'
|
||||
paper-font-headline_-_font-family: '"Orbitron", "Rajdhani", "Exo 2", -apple-system, sans-serif'
|
||||
paper-font-title_-_font-family: '"Orbitron", "Rajdhani", "Exo 2", -apple-system, sans-serif'
|
||||
ha-card-header-font-family: '"Orbitron", "Rajdhani", "Exo 2", -apple-system, sans-serif'
|
||||
|
||||
# ===== background (neon grid, night city) =====
|
||||
lovelace-background: 'radial-gradient(1400px 900px at 10% 15%, rgba(255, 45, 149, 0.28) 0%, transparent 55%), radial-gradient(1200px 800px at 90% 85%, rgba(0, 240, 255, 0.25) 0%, transparent 50%), radial-gradient(800px 600px at 50% 50%, rgba(255, 220, 0, 0.08) 0%, transparent 60%), linear-gradient(135deg, #0a0014 0%, #05010d 50%, #000005 100%)'
|
||||
primary-background-color: '#05010d'
|
||||
secondary-background-color: '#0d0220'
|
||||
app-header-background-color: 'rgba(5, 1, 13, 0.85)'
|
||||
app-header-text-color: '#ffeb00'
|
||||
sidebar-background-color: 'rgba(10, 0, 20, 0.92)'
|
||||
sidebar-text-color: '#d0d0e8'
|
||||
sidebar-selected-text-color: '#00f0ff'
|
||||
sidebar-selected-background-color: 'rgba(255, 45, 149, 0.18)'
|
||||
sidebar-icon-color: '#9a8ec0'
|
||||
sidebar-selected-icon-color: '#00f0ff'
|
||||
|
||||
# ===== card (neon panel) =====
|
||||
ha-card-background: 'rgba(15, 0, 30, 0.92)'
|
||||
card-background-color: 'rgba(15, 0, 30, 0.92)'
|
||||
ha-card-border-radius: '4px'
|
||||
ha-card-border-width: '1px'
|
||||
ha-card-border-color: 'rgba(255, 45, 149, 0.35)'
|
||||
ha-card-box-shadow: '0 0 20px rgba(255, 45, 149, 0.25), 0 0 40px rgba(0, 240, 255, 0.10), inset 0 1px 0 rgba(255, 45, 149, 0.15)'
|
||||
ha-card-header-color: '#ffeb00'
|
||||
|
||||
# ===== more-info dialog / popup (opaque, readable) =====
|
||||
mdc-theme-surface: '#0f001e'
|
||||
mdc-dialog-scrim-color: 'rgba(0, 0, 0, 0.80)'
|
||||
dialog-backdrop-filter: 'blur(10px)'
|
||||
ha-dialog-surface-background: '#0f001e'
|
||||
ha-dialog-border-radius: '4px'
|
||||
|
||||
# ===== text =====
|
||||
primary-text-color: '#e8e8f8'
|
||||
secondary-text-color: '#8a89c0'
|
||||
text-primary-color: '#ffffff'
|
||||
disabled-text-color: '#4a4870'
|
||||
|
||||
# ===== accents =====
|
||||
primary-color: '#ff2d95'
|
||||
accent-color: '#00f0ff'
|
||||
light-primary-color: '#ff7ec2'
|
||||
dark-primary-color: '#c41175'
|
||||
label-badge-background-color: 'rgba(15, 0, 30, 0.90)'
|
||||
label-badge-text-color: '#ffeb00'
|
||||
label-badge-red: '#ff2d95'
|
||||
label-badge-green: '#00ff9c'
|
||||
label-badge-blue: '#00f0ff'
|
||||
label-badge-yellow: '#ffeb00'
|
||||
label-badge-grey: '#8a89c0'
|
||||
|
||||
# ===== state colors =====
|
||||
state-icon-color: '#00f0ff'
|
||||
state-icon-active-color: '#ff2d95'
|
||||
state-icon-unavailable-color: '#4a4870'
|
||||
paper-item-icon-color: '#9a8ec0'
|
||||
paper-item-icon-active-color: '#ff2d95'
|
||||
|
||||
# ===== domain states =====
|
||||
state-binary-sensor-active-color: '#00ff9c'
|
||||
state-light-active-color: '#ffeb00'
|
||||
state-switch-active-color: '#00f0ff'
|
||||
state-fan-active-color: '#00f0ff'
|
||||
state-media-player-active-color: '#ff2d95'
|
||||
state-person-home-color: '#00ff9c'
|
||||
state-person-not_home-color: '#8a89c0'
|
||||
|
||||
# ===== toggles =====
|
||||
switch-checked-color: '#ff2d95'
|
||||
switch-checked-button-color: '#ff7ec2'
|
||||
switch-checked-track-color: 'rgba(255, 45, 149, 0.45)'
|
||||
switch-unchecked-button-color: '#4a4870'
|
||||
switch-unchecked-track-color: 'rgba(74, 72, 112, 0.45)'
|
||||
|
||||
# ===== sliders =====
|
||||
paper-slider-knob-color: '#ff2d95'
|
||||
paper-slider-knob-start-color: '#ff2d95'
|
||||
paper-slider-pin-color: '#ff2d95'
|
||||
paper-slider-active-color: '#00f0ff'
|
||||
paper-slider-container-color: 'rgba(255, 45, 149, 0.30)'
|
||||
paper-slider-secondary-color: '#c41175'
|
||||
|
||||
# ===== dividers / outlines =====
|
||||
divider-color: 'rgba(255, 45, 149, 0.18)'
|
||||
outline-color: 'rgba(0, 240, 255, 0.20)'
|
||||
|
||||
# ===== input elements =====
|
||||
input-background-color: 'rgba(15, 0, 30, 0.80)'
|
||||
input-fill-color: 'rgba(15, 0, 30, 0.80)'
|
||||
input-ink-color: '#e8e8f8'
|
||||
input-label-ink-color: '#8a89c0'
|
||||
input-idle-line-color: 'rgba(255, 45, 149, 0.25)'
|
||||
input-hover-line-color: '#ff2d95'
|
||||
input-focused-line-color: '#00f0ff'
|
||||
|
||||
# ===== MDC select + text-field (dropdowns inside cards/dialogs) =====
|
||||
mdc-select-fill-color: 'rgba(15, 0, 30, 0.90)'
|
||||
mdc-select-ink-color: '#e8e8f8'
|
||||
mdc-select-label-ink-color: '#8a89c0'
|
||||
mdc-select-dropdown-icon-color: '#00f0ff'
|
||||
mdc-select-idle-line-color: 'rgba(255, 45, 149, 0.35)'
|
||||
mdc-select-hover-line-color: '#ff2d95'
|
||||
mdc-select-focused-label-color: '#00f0ff'
|
||||
mdc-text-field-fill-color: 'rgba(15, 0, 30, 0.90)'
|
||||
mdc-text-field-ink-color: '#e8e8f8'
|
||||
mdc-text-field-label-ink-color: '#8a89c0'
|
||||
mdc-text-field-idle-line-color: 'rgba(255, 45, 149, 0.35)'
|
||||
mdc-text-field-hover-line-color: '#ff2d95'
|
||||
mdc-text-field-focused-label-color: '#00f0ff'
|
||||
mdc-text-field-disabled-fill-color: 'rgba(15, 0, 30, 0.60)'
|
||||
mdc-text-field-disabled-ink-color: '#8a89c0'
|
||||
mdc-filled-text-field-container-color: 'rgba(15, 0, 30, 0.90)'
|
||||
mdc-filled-text-field-label-text-color: '#8a89c0'
|
||||
mdc-filled-text-field-input-text-color: '#e8e8f8'
|
||||
ha-textfield-background: 'rgba(15, 0, 30, 0.90)'
|
||||
|
||||
card-mod-theme: cyberpunk
|
||||
card-mod-root: |
|
||||
ha-voice-command-dialog $ ha-textfield {
|
||||
--mdc-text-field-fill-color: rgba(15, 0, 30, 0.95) !important;
|
||||
--mdc-text-field-ink-color: #e8e8f8 !important;
|
||||
--mdc-text-field-label-ink-color: #8a89c0 !important;
|
||||
}
|
||||
ha-voice-command-dialog $ ha-textfield $ .mdc-text-field__input {
|
||||
color: #e8e8f8 !important;
|
||||
}
|
||||
|
||||
# ===== buttons =====
|
||||
mdc-theme-primary: '#ff2d95'
|
||||
mdc-theme-secondary: '#00f0ff'
|
||||
mdc-theme-on-primary: '#ffffff'
|
||||
mdc-theme-on-secondary: '#000000'
|
||||
|
||||
# ===== tables =====
|
||||
table-row-background-color: 'transparent'
|
||||
table-row-alternative-background-color: 'rgba(255, 45, 149, 0.04)'
|
||||
154
hosts/physical/concord-nuc/homeassistant/themes/glass_exo.yaml
Normal file
154
hosts/physical/concord-nuc/homeassistant/themes/glass_exo.yaml
Normal file
@@ -0,0 +1,154 @@
|
||||
glass_exo:
|
||||
# ===== font =====
|
||||
primary-font-family: '"Exo 2", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
||||
paper-font-common-base_-_font-family: '"Exo 2", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
||||
paper-font-body1_-_font-family: '"Exo 2", "SF Pro Display", -apple-system, sans-serif'
|
||||
paper-font-subhead_-_font-family: '"Exo 2", "SF Pro Display", -apple-system, sans-serif'
|
||||
paper-font-headline_-_font-family: '"Exo 2", "SF Pro Display", -apple-system, sans-serif'
|
||||
paper-font-title_-_font-family: '"Exo 2", "SF Pro Display", -apple-system, sans-serif'
|
||||
ha-card-header-font-family: '"Exo 2", "SF Pro Display", -apple-system, sans-serif'
|
||||
paper-font-common-base_-_-webkit-font-smoothing: 'antialiased'
|
||||
|
||||
# ===== background (gradient + soft glow) =====
|
||||
lovelace-background: 'radial-gradient(1200px 800px at 15% 10%, rgba(99, 102, 241, 0.20) 0%, transparent 60%), radial-gradient(1000px 700px at 85% 90%, rgba(236, 72, 153, 0.18) 0%, transparent 55%), linear-gradient(135deg, #0b0f1a 0%, #0a0a14 45%, #060610 100%)'
|
||||
primary-background-color: '#0a0a14'
|
||||
secondary-background-color: '#10121c'
|
||||
app-header-background-color: 'rgba(10, 10, 20, 0.65)'
|
||||
app-header-text-color: '#e7e9f4'
|
||||
sidebar-background-color: 'rgba(10, 10, 20, 0.85)'
|
||||
sidebar-text-color: '#c7c9dc'
|
||||
sidebar-selected-text-color: '#a78bfa'
|
||||
sidebar-selected-background-color: 'rgba(167, 139, 250, 0.12)'
|
||||
sidebar-icon-color: '#8a8db0'
|
||||
sidebar-selected-icon-color: '#a78bfa'
|
||||
|
||||
# ===== card (glass panel) =====
|
||||
ha-card-background: 'rgba(22, 24, 40, 0.88)'
|
||||
card-background-color: 'rgba(22, 24, 40, 0.88)'
|
||||
ha-card-border-radius: '18px'
|
||||
ha-card-border-width: '1px'
|
||||
ha-card-border-color: 'rgba(255, 255, 255, 0.08)'
|
||||
ha-card-box-shadow: '0 8px 32px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.04)'
|
||||
ha-card-header-color: '#e7e9f4'
|
||||
|
||||
# ===== more-info dialog / popup (opaque, readable) =====
|
||||
mdc-theme-surface: '#161828'
|
||||
mdc-dialog-scrim-color: 'rgba(0, 0, 0, 0.78)'
|
||||
dialog-backdrop-filter: 'blur(12px)'
|
||||
ha-dialog-surface-background: '#161828'
|
||||
ha-dialog-border-radius: '20px'
|
||||
|
||||
# ===== text =====
|
||||
primary-text-color: '#e7e9f4'
|
||||
secondary-text-color: '#9a9cb8'
|
||||
text-primary-color: '#ffffff'
|
||||
disabled-text-color: '#5b5e78'
|
||||
|
||||
# ===== accents =====
|
||||
primary-color: '#a78bfa'
|
||||
accent-color: '#f472b6'
|
||||
light-primary-color: '#c4b5fd'
|
||||
dark-primary-color: '#7c3aed'
|
||||
label-badge-background-color: 'rgba(22, 24, 40, 0.75)'
|
||||
label-badge-text-color: '#e7e9f4'
|
||||
label-badge-red: '#f87171'
|
||||
label-badge-green: '#4ade80'
|
||||
label-badge-blue: '#60a5fa'
|
||||
label-badge-yellow: '#fbbf24'
|
||||
label-badge-grey: '#9ca3af'
|
||||
|
||||
# ===== state colors =====
|
||||
state-icon-color: '#a78bfa'
|
||||
state-icon-active-color: '#f472b6'
|
||||
state-icon-unavailable-color: '#6b7280'
|
||||
paper-item-icon-color: '#9a9cb8'
|
||||
paper-item-icon-active-color: '#f472b6'
|
||||
|
||||
# ===== domain states =====
|
||||
state-binary-sensor-active-color: '#4ade80'
|
||||
state-light-active-color: '#fbbf24'
|
||||
state-switch-active-color: '#60a5fa'
|
||||
state-fan-active-color: '#38bdf8'
|
||||
state-climate-cooling-color: '#60a5fa'
|
||||
state-climate-heating-color: '#f87171'
|
||||
state-media-player-active-color: '#f472b6'
|
||||
state-person-home-color: '#4ade80'
|
||||
state-person-not_home-color: '#9ca3af'
|
||||
|
||||
# ===== toggles / switches =====
|
||||
switch-checked-color: '#a78bfa'
|
||||
switch-checked-button-color: '#c4b5fd'
|
||||
switch-checked-track-color: 'rgba(167, 139, 250, 0.4)'
|
||||
switch-unchecked-button-color: '#6b7280'
|
||||
switch-unchecked-track-color: 'rgba(107, 114, 128, 0.4)'
|
||||
|
||||
# ===== sliders =====
|
||||
paper-slider-knob-color: '#a78bfa'
|
||||
paper-slider-knob-start-color: '#a78bfa'
|
||||
paper-slider-pin-color: '#a78bfa'
|
||||
paper-slider-active-color: '#a78bfa'
|
||||
paper-slider-container-color: 'rgba(167, 139, 250, 0.25)'
|
||||
paper-slider-secondary-color: '#7c3aed'
|
||||
|
||||
# ===== dividers / outlines =====
|
||||
divider-color: 'rgba(255, 255, 255, 0.08)'
|
||||
outline-color: 'rgba(255, 255, 255, 0.10)'
|
||||
|
||||
# ===== input elements =====
|
||||
input-background-color: 'rgba(22, 24, 40, 0.70)'
|
||||
input-fill-color: 'rgba(22, 24, 40, 0.70)'
|
||||
input-ink-color: '#e7e9f4'
|
||||
input-label-ink-color: '#9a9cb8'
|
||||
input-idle-line-color: 'rgba(255, 255, 255, 0.12)'
|
||||
input-hover-line-color: '#a78bfa'
|
||||
input-focused-line-color: '#a78bfa'
|
||||
|
||||
# ===== MDC select + text-field (dropdowns inside cards/dialogs) =====
|
||||
mdc-select-fill-color: 'rgba(22, 24, 40, 0.85)'
|
||||
mdc-select-ink-color: '#e7e9f4'
|
||||
mdc-select-label-ink-color: '#9a9cb8'
|
||||
mdc-select-dropdown-icon-color: '#a78bfa'
|
||||
mdc-select-idle-line-color: 'rgba(255, 255, 255, 0.18)'
|
||||
mdc-select-hover-line-color: '#a78bfa'
|
||||
mdc-select-focused-label-color: '#a78bfa'
|
||||
mdc-text-field-fill-color: 'rgba(22, 24, 40, 0.85)'
|
||||
mdc-text-field-ink-color: '#e7e9f4'
|
||||
mdc-text-field-label-ink-color: '#9a9cb8'
|
||||
mdc-text-field-idle-line-color: 'rgba(255, 255, 255, 0.18)'
|
||||
mdc-text-field-hover-line-color: '#a78bfa'
|
||||
mdc-text-field-focused-label-color: '#a78bfa'
|
||||
mdc-text-field-disabled-fill-color: 'rgba(22, 24, 40, 0.60)'
|
||||
mdc-text-field-disabled-ink-color: '#9a9cb8'
|
||||
mdc-text-field-outlined-idle-border-color: 'rgba(255,255,255,0.18)'
|
||||
mdc-text-field-outlined-hover-border-color: '#a78bfa'
|
||||
mdc-filled-text-field-container-color: 'rgba(22, 24, 40, 0.85)'
|
||||
mdc-filled-text-field-label-text-color: '#9a9cb8'
|
||||
mdc-filled-text-field-input-text-color: '#e7e9f4'
|
||||
ha-textfield-background: 'rgba(22, 24, 40, 0.85)'
|
||||
|
||||
# ===== Assist voice-command dialog — card-mod CSS override =====
|
||||
card-mod-theme: glass_exo
|
||||
card-mod-root: |
|
||||
ha-voice-command-dialog $ ha-textfield {
|
||||
--mdc-text-field-fill-color: rgba(22, 24, 40, 0.92) !important;
|
||||
--mdc-text-field-ink-color: #e7e9f4 !important;
|
||||
--mdc-text-field-label-ink-color: #9a9cb8 !important;
|
||||
--mdc-text-field-idle-line-color: rgba(255,255,255,0.18) !important;
|
||||
}
|
||||
ha-voice-command-dialog $ ha-textfield $ .mdc-text-field__input {
|
||||
color: #e7e9f4 !important;
|
||||
}
|
||||
|
||||
# ===== buttons =====
|
||||
mdc-theme-primary: '#a78bfa'
|
||||
mdc-theme-secondary: '#f472b6'
|
||||
mdc-theme-on-primary: '#ffffff'
|
||||
mdc-theme-on-secondary: '#ffffff'
|
||||
|
||||
# ===== code / graph =====
|
||||
code-editor-background-color: 'rgba(10, 10, 20, 0.85)'
|
||||
graph-base-color: '#a78bfa'
|
||||
|
||||
# ===== tables =====
|
||||
table-row-background-color: 'transparent'
|
||||
table-row-alternative-background-color: 'rgba(255, 255, 255, 0.02)'
|
||||
143
hosts/physical/concord-nuc/homeassistant/themes/samurai.yaml
Normal file
143
hosts/physical/concord-nuc/homeassistant/themes/samurai.yaml
Normal file
@@ -0,0 +1,143 @@
|
||||
samurai:
|
||||
# ===== font (classical Japanese + serif) =====
|
||||
primary-font-family: '"Noto Serif JP", "Sawarabi Mincho", "Shippori Mincho", "Cormorant Garamond", Georgia, serif'
|
||||
paper-font-common-base_-_font-family: '"Noto Serif JP", "Sawarabi Mincho", Georgia, serif'
|
||||
paper-font-body1_-_font-family: '"Noto Serif JP", "Sawarabi Mincho", Georgia, serif'
|
||||
paper-font-subhead_-_font-family: '"Noto Serif JP", "Shippori Mincho", Georgia, serif'
|
||||
paper-font-headline_-_font-family: '"Shippori Mincho", "Noto Serif JP", Georgia, serif'
|
||||
paper-font-title_-_font-family: '"Shippori Mincho", "Noto Serif JP", Georgia, serif'
|
||||
ha-card-header-font-family: '"Shippori Mincho", "Noto Serif JP", Georgia, serif'
|
||||
|
||||
# ===== background (sumi ink + rising sun) =====
|
||||
lovelace-background: 'radial-gradient(900px 700px at 85% 15%, rgba(196, 30, 58, 0.22) 0%, transparent 50%), radial-gradient(1100px 800px at 15% 85%, rgba(212, 175, 55, 0.10) 0%, transparent 55%), linear-gradient(170deg, #0d0d0d 0%, #0a0508 50%, #050000 100%)'
|
||||
primary-background-color: '#0a0508'
|
||||
secondary-background-color: '#13090c'
|
||||
app-header-background-color: 'rgba(10, 5, 8, 0.88)'
|
||||
app-header-text-color: '#d4af37'
|
||||
sidebar-background-color: 'rgba(10, 5, 8, 0.94)'
|
||||
sidebar-text-color: '#c8bfa8'
|
||||
sidebar-selected-text-color: '#d4af37'
|
||||
sidebar-selected-background-color: 'rgba(196, 30, 58, 0.18)'
|
||||
sidebar-icon-color: '#8a8070'
|
||||
sidebar-selected-icon-color: '#c41e3a'
|
||||
|
||||
# ===== card (rice paper on charcoal) =====
|
||||
ha-card-background: 'rgba(20, 13, 14, 0.94)'
|
||||
card-background-color: 'rgba(20, 13, 14, 0.94)'
|
||||
ha-card-border-radius: '2px'
|
||||
ha-card-border-width: '1px'
|
||||
ha-card-border-color: 'rgba(212, 175, 55, 0.20)'
|
||||
ha-card-box-shadow: '0 4px 20px rgba(0, 0, 0, 0.60), inset 0 1px 0 rgba(212, 175, 55, 0.08)'
|
||||
ha-card-header-color: '#d4af37'
|
||||
|
||||
# ===== more-info dialog / popup (opaque, readable) =====
|
||||
mdc-theme-surface: '#140d0e'
|
||||
mdc-dialog-scrim-color: 'rgba(0, 0, 0, 0.82)'
|
||||
dialog-backdrop-filter: 'blur(10px)'
|
||||
ha-dialog-surface-background: '#140d0e'
|
||||
ha-dialog-border-radius: '2px'
|
||||
|
||||
# ===== text =====
|
||||
primary-text-color: '#ebe3d0'
|
||||
secondary-text-color: '#9a8f7a'
|
||||
text-primary-color: '#ffffff'
|
||||
disabled-text-color: '#4a4238'
|
||||
|
||||
# ===== accents =====
|
||||
primary-color: '#c41e3a'
|
||||
accent-color: '#d4af37'
|
||||
light-primary-color: '#e34f65'
|
||||
dark-primary-color: '#8b1528'
|
||||
label-badge-background-color: 'rgba(20, 13, 14, 0.92)'
|
||||
label-badge-text-color: '#d4af37'
|
||||
label-badge-red: '#c41e3a'
|
||||
label-badge-green: '#728a5c'
|
||||
label-badge-blue: '#4a6a7c'
|
||||
label-badge-yellow: '#d4af37'
|
||||
label-badge-grey: '#8a8070'
|
||||
|
||||
# ===== state colors =====
|
||||
state-icon-color: '#d4af37'
|
||||
state-icon-active-color: '#c41e3a'
|
||||
state-icon-unavailable-color: '#4a4238'
|
||||
paper-item-icon-color: '#9a8f7a'
|
||||
paper-item-icon-active-color: '#c41e3a'
|
||||
|
||||
# ===== domain states =====
|
||||
state-binary-sensor-active-color: '#728a5c'
|
||||
state-light-active-color: '#d4af37'
|
||||
state-switch-active-color: '#c41e3a'
|
||||
state-fan-active-color: '#4a6a7c'
|
||||
state-media-player-active-color: '#c41e3a'
|
||||
state-person-home-color: '#728a5c'
|
||||
state-person-not_home-color: '#8a8070'
|
||||
|
||||
# ===== toggles =====
|
||||
switch-checked-color: '#c41e3a'
|
||||
switch-checked-button-color: '#e34f65'
|
||||
switch-checked-track-color: 'rgba(196, 30, 58, 0.45)'
|
||||
switch-unchecked-button-color: '#4a4238'
|
||||
switch-unchecked-track-color: 'rgba(74, 66, 56, 0.45)'
|
||||
|
||||
# ===== sliders =====
|
||||
paper-slider-knob-color: '#c41e3a'
|
||||
paper-slider-knob-start-color: '#c41e3a'
|
||||
paper-slider-pin-color: '#d4af37'
|
||||
paper-slider-active-color: '#d4af37'
|
||||
paper-slider-container-color: 'rgba(196, 30, 58, 0.30)'
|
||||
paper-slider-secondary-color: '#8b1528'
|
||||
|
||||
# ===== dividers / outlines =====
|
||||
divider-color: 'rgba(212, 175, 55, 0.15)'
|
||||
outline-color: 'rgba(212, 175, 55, 0.18)'
|
||||
|
||||
# ===== input elements =====
|
||||
input-background-color: 'rgba(20, 13, 14, 0.85)'
|
||||
input-fill-color: 'rgba(20, 13, 14, 0.85)'
|
||||
input-ink-color: '#ebe3d0'
|
||||
input-label-ink-color: '#9a8f7a'
|
||||
input-idle-line-color: 'rgba(212, 175, 55, 0.22)'
|
||||
input-hover-line-color: '#d4af37'
|
||||
input-focused-line-color: '#c41e3a'
|
||||
|
||||
# ===== MDC select + text-field (dropdowns inside cards/dialogs) =====
|
||||
mdc-select-fill-color: 'rgba(20, 13, 14, 0.92)'
|
||||
mdc-select-ink-color: '#ebe3d0'
|
||||
mdc-select-label-ink-color: '#9a8f7a'
|
||||
mdc-select-dropdown-icon-color: '#d4af37'
|
||||
mdc-select-idle-line-color: 'rgba(212, 175, 55, 0.30)'
|
||||
mdc-select-hover-line-color: '#d4af37'
|
||||
mdc-select-focused-label-color: '#c41e3a'
|
||||
mdc-text-field-fill-color: 'rgba(20, 13, 14, 0.92)'
|
||||
mdc-text-field-ink-color: '#ebe3d0'
|
||||
mdc-text-field-label-ink-color: '#9a8f7a'
|
||||
mdc-text-field-idle-line-color: 'rgba(212, 175, 55, 0.30)'
|
||||
mdc-text-field-hover-line-color: '#d4af37'
|
||||
mdc-text-field-focused-label-color: '#c41e3a'
|
||||
mdc-text-field-disabled-fill-color: 'rgba(20, 13, 14, 0.60)'
|
||||
mdc-text-field-disabled-ink-color: '#9a8f7a'
|
||||
mdc-filled-text-field-container-color: 'rgba(20, 13, 14, 0.94)'
|
||||
mdc-filled-text-field-label-text-color: '#9a8f7a'
|
||||
mdc-filled-text-field-input-text-color: '#ebe3d0'
|
||||
ha-textfield-background: 'rgba(20, 13, 14, 0.94)'
|
||||
|
||||
card-mod-theme: samurai
|
||||
card-mod-root: |
|
||||
ha-voice-command-dialog $ ha-textfield {
|
||||
--mdc-text-field-fill-color: rgba(20, 13, 14, 0.96) !important;
|
||||
--mdc-text-field-ink-color: #ebe3d0 !important;
|
||||
--mdc-text-field-label-ink-color: #9a8f7a !important;
|
||||
}
|
||||
ha-voice-command-dialog $ ha-textfield $ .mdc-text-field__input {
|
||||
color: #ebe3d0 !important;
|
||||
}
|
||||
|
||||
# ===== buttons =====
|
||||
mdc-theme-primary: '#c41e3a'
|
||||
mdc-theme-secondary: '#d4af37'
|
||||
mdc-theme-on-primary: '#ffffff'
|
||||
mdc-theme-on-secondary: '#0a0508'
|
||||
|
||||
# ===== tables =====
|
||||
table-row-background-color: 'transparent'
|
||||
table-row-alternative-background-color: 'rgba(212, 175, 55, 0.04)'
|
||||
143
hosts/physical/concord-nuc/homeassistant/themes/steampunk.yaml
Normal file
143
hosts/physical/concord-nuc/homeassistant/themes/steampunk.yaml
Normal file
@@ -0,0 +1,143 @@
|
||||
steampunk:
|
||||
# ===== font (classical, brass-engraved feel) =====
|
||||
primary-font-family: '"Cormorant Garamond", "Playfair Display", "Crimson Text", Georgia, "Times New Roman", serif'
|
||||
paper-font-common-base_-_font-family: '"Cormorant Garamond", "Playfair Display", Georgia, serif'
|
||||
paper-font-body1_-_font-family: '"Cormorant Garamond", "Crimson Text", Georgia, serif'
|
||||
paper-font-subhead_-_font-family: '"Playfair Display", "Cormorant Garamond", Georgia, serif'
|
||||
paper-font-headline_-_font-family: '"Playfair Display", "Cormorant Garamond", Georgia, serif'
|
||||
paper-font-title_-_font-family: '"Playfair Display", "Cormorant Garamond", Georgia, serif'
|
||||
ha-card-header-font-family: '"Playfair Display", "Cormorant Garamond", Georgia, serif'
|
||||
|
||||
# ===== background (aged leather, gaslight) =====
|
||||
lovelace-background: 'radial-gradient(1200px 800px at 20% 20%, rgba(201, 166, 107, 0.18) 0%, transparent 55%), radial-gradient(1000px 700px at 80% 80%, rgba(184, 115, 51, 0.15) 0%, transparent 55%), linear-gradient(135deg, #2a1810 0%, #1a0e08 50%, #0e0704 100%)'
|
||||
primary-background-color: '#1a0e08'
|
||||
secondary-background-color: '#241610'
|
||||
app-header-background-color: 'rgba(26, 14, 8, 0.85)'
|
||||
app-header-text-color: '#d9b381'
|
||||
sidebar-background-color: 'rgba(26, 14, 8, 0.92)'
|
||||
sidebar-text-color: '#c7a870'
|
||||
sidebar-selected-text-color: '#e8c98c'
|
||||
sidebar-selected-background-color: 'rgba(201, 166, 107, 0.15)'
|
||||
sidebar-icon-color: '#8a6e46'
|
||||
sidebar-selected-icon-color: '#d9895f'
|
||||
|
||||
# ===== card (aged parchment panel) =====
|
||||
ha-card-background: 'rgba(38, 24, 16, 0.93)'
|
||||
card-background-color: 'rgba(38, 24, 16, 0.93)'
|
||||
ha-card-border-radius: '6px'
|
||||
ha-card-border-width: '1px'
|
||||
ha-card-border-color: 'rgba(201, 166, 107, 0.25)'
|
||||
ha-card-box-shadow: '0 6px 24px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(201, 166, 107, 0.12)'
|
||||
ha-card-header-color: '#e8c98c'
|
||||
|
||||
# ===== more-info dialog / popup (opaque, readable) =====
|
||||
mdc-theme-surface: '#261810'
|
||||
mdc-dialog-scrim-color: 'rgba(0, 0, 0, 0.80)'
|
||||
dialog-backdrop-filter: 'blur(10px)'
|
||||
ha-dialog-surface-background: '#261810'
|
||||
ha-dialog-border-radius: '8px'
|
||||
|
||||
# ===== text =====
|
||||
primary-text-color: '#f0dcaf'
|
||||
secondary-text-color: '#b89a66'
|
||||
text-primary-color: '#2a1810'
|
||||
disabled-text-color: '#6a5538'
|
||||
|
||||
# ===== accents =====
|
||||
primary-color: '#c9a66b'
|
||||
accent-color: '#d9895f'
|
||||
light-primary-color: '#e8c98c'
|
||||
dark-primary-color: '#8a6e46'
|
||||
label-badge-background-color: 'rgba(38, 24, 16, 0.90)'
|
||||
label-badge-text-color: '#e8c98c'
|
||||
label-badge-red: '#c44a2a'
|
||||
label-badge-green: '#6b8f47'
|
||||
label-badge-blue: '#4a7688'
|
||||
label-badge-yellow: '#d9b54f'
|
||||
label-badge-grey: '#8a6e46'
|
||||
|
||||
# ===== state colors =====
|
||||
state-icon-color: '#c9a66b'
|
||||
state-icon-active-color: '#d9895f'
|
||||
state-icon-unavailable-color: '#6a5538'
|
||||
paper-item-icon-color: '#b89a66'
|
||||
paper-item-icon-active-color: '#d9895f'
|
||||
|
||||
# ===== domain states =====
|
||||
state-binary-sensor-active-color: '#8fa054'
|
||||
state-light-active-color: '#e8b94a'
|
||||
state-switch-active-color: '#c9a66b'
|
||||
state-fan-active-color: '#c9a66b'
|
||||
state-media-player-active-color: '#d9895f'
|
||||
state-person-home-color: '#8fa054'
|
||||
state-person-not_home-color: '#8a6e46'
|
||||
|
||||
# ===== toggles =====
|
||||
switch-checked-color: '#d9895f'
|
||||
switch-checked-button-color: '#e8c98c'
|
||||
switch-checked-track-color: 'rgba(217, 137, 95, 0.45)'
|
||||
switch-unchecked-button-color: '#6a5538'
|
||||
switch-unchecked-track-color: 'rgba(106, 85, 56, 0.45)'
|
||||
|
||||
# ===== sliders =====
|
||||
paper-slider-knob-color: '#d9895f'
|
||||
paper-slider-knob-start-color: '#d9895f'
|
||||
paper-slider-pin-color: '#c9a66b'
|
||||
paper-slider-active-color: '#c9a66b'
|
||||
paper-slider-container-color: 'rgba(201, 166, 107, 0.30)'
|
||||
paper-slider-secondary-color: '#8a6e46'
|
||||
|
||||
# ===== dividers / outlines =====
|
||||
divider-color: 'rgba(201, 166, 107, 0.20)'
|
||||
outline-color: 'rgba(201, 166, 107, 0.22)'
|
||||
|
||||
# ===== input elements =====
|
||||
input-background-color: 'rgba(38, 24, 16, 0.80)'
|
||||
input-fill-color: 'rgba(38, 24, 16, 0.80)'
|
||||
input-ink-color: '#f0dcaf'
|
||||
input-label-ink-color: '#b89a66'
|
||||
input-idle-line-color: 'rgba(201, 166, 107, 0.30)'
|
||||
input-hover-line-color: '#c9a66b'
|
||||
input-focused-line-color: '#d9895f'
|
||||
|
||||
# ===== MDC select + text-field (dropdowns inside cards/dialogs) =====
|
||||
mdc-select-fill-color: 'rgba(38, 24, 16, 0.90)'
|
||||
mdc-select-ink-color: '#f0dcaf'
|
||||
mdc-select-label-ink-color: '#b89a66'
|
||||
mdc-select-dropdown-icon-color: '#d9895f'
|
||||
mdc-select-idle-line-color: 'rgba(201, 166, 107, 0.35)'
|
||||
mdc-select-hover-line-color: '#c9a66b'
|
||||
mdc-select-focused-label-color: '#d9895f'
|
||||
mdc-text-field-fill-color: 'rgba(38, 24, 16, 0.90)'
|
||||
mdc-text-field-ink-color: '#f0dcaf'
|
||||
mdc-text-field-label-ink-color: '#b89a66'
|
||||
mdc-text-field-idle-line-color: 'rgba(201, 166, 107, 0.35)'
|
||||
mdc-text-field-hover-line-color: '#c9a66b'
|
||||
mdc-text-field-focused-label-color: '#d9895f'
|
||||
mdc-text-field-disabled-fill-color: 'rgba(38, 24, 16, 0.60)'
|
||||
mdc-text-field-disabled-ink-color: '#b89a66'
|
||||
mdc-filled-text-field-container-color: 'rgba(38, 24, 16, 0.92)'
|
||||
mdc-filled-text-field-label-text-color: '#b89a66'
|
||||
mdc-filled-text-field-input-text-color: '#f0dcaf'
|
||||
ha-textfield-background: 'rgba(38, 24, 16, 0.92)'
|
||||
|
||||
card-mod-theme: steampunk
|
||||
card-mod-root: |
|
||||
ha-voice-command-dialog $ ha-textfield {
|
||||
--mdc-text-field-fill-color: rgba(38, 24, 16, 0.95) !important;
|
||||
--mdc-text-field-ink-color: #f0dcaf !important;
|
||||
--mdc-text-field-label-ink-color: #b89a66 !important;
|
||||
}
|
||||
ha-voice-command-dialog $ ha-textfield $ .mdc-text-field__input {
|
||||
color: #f0dcaf !important;
|
||||
}
|
||||
|
||||
# ===== buttons =====
|
||||
mdc-theme-primary: '#c9a66b'
|
||||
mdc-theme-secondary: '#d9895f'
|
||||
mdc-theme-on-primary: '#1a0e08'
|
||||
mdc-theme-on-secondary: '#1a0e08'
|
||||
|
||||
# ===== tables =====
|
||||
table-row-background-color: 'transparent'
|
||||
table-row-alternative-background-color: 'rgba(201, 166, 107, 0.05)'
|
||||
201
hosts/physical/concord-nuc/homeassistant/www/assist-fix.js
Normal file
201
hosts/physical/concord-nuc/homeassistant/www/assist-fix.js
Normal file
@@ -0,0 +1,201 @@
|
||||
// Assist textfield readability — v5.
|
||||
//
|
||||
// The new ha-input wraps Web Awesome (wa-input). Web Awesome is a separate
|
||||
// component system that renders LIGHT by default unless told to be dark via:
|
||||
// 1) adding the "wa-dark" class on the root / document element, or
|
||||
// 2) setting its own --wa-* CSS color tokens at :root.
|
||||
//
|
||||
// HA's dark theme flag doesn't propagate to it, so we do both: force wa-dark
|
||||
// class on the html element AND override the wa color tokens to match the
|
||||
// active HA theme. Plus inline-style belt-and-suspenders on the actual input.
|
||||
(function () {
|
||||
if (window.__assistFixLoaded) return;
|
||||
window.__assistFixLoaded = true;
|
||||
|
||||
function v(name, fallback) {
|
||||
const x = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(name).trim();
|
||||
return x || fallback;
|
||||
}
|
||||
|
||||
function applyGlobalTokens() {
|
||||
const fg = v('--primary-text-color', '#e7e9f4');
|
||||
const bg = v('--card-background-color', 'rgba(22, 24, 40, 0.92)');
|
||||
const bgElev = v('--secondary-background-color', 'rgba(30, 32, 50, 0.95)');
|
||||
const muted = v('--secondary-text-color', '#9a9cb8');
|
||||
const border = v('--divider-color', 'rgba(255,255,255,0.20)');
|
||||
const accent = v('--primary-color', '#a78bfa');
|
||||
|
||||
// 1. Force Web Awesome dark mode class on the html element
|
||||
document.documentElement.classList.add('wa-dark');
|
||||
|
||||
// 2. Inject/refresh a <style> with wa-* color tokens mapped to HA theme
|
||||
let el = document.getElementById('assist-fix-wa-tokens');
|
||||
if (!el) {
|
||||
el = document.createElement('style');
|
||||
el.id = 'assist-fix-wa-tokens';
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
el.textContent = `
|
||||
:root, html.wa-dark, .wa-dark {
|
||||
/* Web Awesome text tokens */
|
||||
--wa-color-text: ${fg};
|
||||
--wa-color-text-link: ${accent};
|
||||
--wa-color-text-quiet: ${muted};
|
||||
--wa-color-text-normal: ${fg};
|
||||
--wa-color-on-quiet: ${fg};
|
||||
--wa-color-on-normal: ${fg};
|
||||
/* Surfaces */
|
||||
--wa-color-surface-default: ${bg};
|
||||
--wa-color-surface-raised: ${bgElev};
|
||||
--wa-color-surface-lowered: ${bg};
|
||||
--wa-color-surface-border: ${border};
|
||||
/* Fills (inputs, buttons) */
|
||||
--wa-color-fill-quiet: ${bg};
|
||||
--wa-color-fill-normal: ${bg};
|
||||
--wa-color-fill-loud: ${accent};
|
||||
/* Borders */
|
||||
--wa-color-border-quiet: ${border};
|
||||
--wa-color-border-normal: ${border};
|
||||
--wa-color-border-loud: ${accent};
|
||||
/* Brand (accent) */
|
||||
--wa-color-brand-on-quiet: ${fg};
|
||||
--wa-color-brand-on-normal: ${fg};
|
||||
--wa-color-brand-fill-quiet: ${bg};
|
||||
--wa-color-brand-fill-normal: ${accent};
|
||||
--wa-color-brand-fill-loud: ${accent};
|
||||
--wa-color-brand-border-quiet: ${border};
|
||||
--wa-color-brand-border-normal: ${accent};
|
||||
/* Neutral scale some components read */
|
||||
--wa-color-neutral-fill-quiet: ${bg};
|
||||
--wa-color-neutral-fill-normal: ${bg};
|
||||
--wa-color-neutral-on-quiet: ${fg};
|
||||
--wa-color-neutral-on-normal: ${fg};
|
||||
--wa-color-neutral-border-quiet: ${border};
|
||||
--wa-color-neutral-border-normal: ${border};
|
||||
|
||||
/* Shoelace-inspired fallbacks (some versions) */
|
||||
--sl-color-neutral-0: ${bg};
|
||||
--sl-color-neutral-50: ${bgElev};
|
||||
--sl-color-neutral-100: ${bg};
|
||||
--sl-color-neutral-200: ${border};
|
||||
--sl-color-neutral-500: ${muted};
|
||||
--sl-color-neutral-700: ${fg};
|
||||
--sl-color-neutral-900: ${fg};
|
||||
--sl-color-neutral-1000: ${fg};
|
||||
--sl-input-background-color: ${bg};
|
||||
--sl-input-color: ${fg};
|
||||
--sl-input-border-color: ${border};
|
||||
--sl-input-label-color: ${muted};
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function patchHaInput(haInput) {
|
||||
if (!haInput || haInput.__assistFixed) return;
|
||||
const sr = haInput.shadowRoot;
|
||||
if (!sr) return;
|
||||
haInput.__assistFixed = true;
|
||||
const fg = v('--primary-text-color', '#e7e9f4');
|
||||
const bg = v('--card-background-color', 'rgba(22, 24, 40, 0.92)');
|
||||
const muted = v('--secondary-text-color', '#9a9cb8');
|
||||
const border = v('--divider-color', 'rgba(255,255,255,0.20)');
|
||||
|
||||
// CSS via exported parts of wa-input
|
||||
const outer = document.createElement('style');
|
||||
outer.textContent = `
|
||||
:host, wa-input { color: ${fg} !important; }
|
||||
wa-input::part(base) {
|
||||
background-color: ${bg} !important;
|
||||
border-color: ${border} !important;
|
||||
color: ${fg} !important;
|
||||
}
|
||||
wa-input::part(input) {
|
||||
color: ${fg} !important;
|
||||
caret-color: ${fg} !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
wa-input::part(label),
|
||||
wa-input::part(form-control-label) {
|
||||
color: ${muted} !important;
|
||||
}
|
||||
wa-input::part(hint) { color: ${muted} !important; }
|
||||
`;
|
||||
sr.appendChild(outer);
|
||||
|
||||
// Inline styles on the wa-input's internals (deepest possible)
|
||||
const wa = sr.querySelector('wa-input');
|
||||
if (wa && wa.shadowRoot) {
|
||||
const innerStyle = document.createElement('style');
|
||||
innerStyle.textContent = `
|
||||
:host { color: ${fg} !important; }
|
||||
.text-field, div[part="base"] {
|
||||
background-color: ${bg} !important;
|
||||
border-color: ${border} !important;
|
||||
}
|
||||
input, input.control {
|
||||
color: ${fg} !important;
|
||||
caret-color: ${fg} !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
input::placeholder { color: ${muted} !important; opacity: 0.7; }
|
||||
label, .label { color: ${muted} !important; }
|
||||
`;
|
||||
wa.shadowRoot.appendChild(innerStyle);
|
||||
|
||||
const input = wa.shadowRoot.querySelector('input');
|
||||
if (input) {
|
||||
input.style.setProperty('color', fg, 'important');
|
||||
input.style.setProperty('caret-color', fg, 'important');
|
||||
input.style.setProperty('background-color', 'transparent', 'important');
|
||||
}
|
||||
const base = wa.shadowRoot.querySelector('[part="base"]');
|
||||
if (base) {
|
||||
base.style.setProperty('background-color', bg, 'important');
|
||||
base.style.setProperty('border-color', border, 'important');
|
||||
base.style.setProperty('color', fg, 'important');
|
||||
}
|
||||
const label = wa.shadowRoot.querySelector('.label, [part~="label"]');
|
||||
if (label) label.style.setProperty('color', muted, 'important');
|
||||
}
|
||||
}
|
||||
|
||||
function walk(root, depth = 0) {
|
||||
if (!root || depth > 12) return 0;
|
||||
let n = 0;
|
||||
try {
|
||||
root.querySelectorAll('ha-input').forEach(el => { patchHaInput(el); n++; });
|
||||
root.querySelectorAll('*').forEach(el => {
|
||||
if (el.shadowRoot) n += walk(el.shadowRoot, depth + 1);
|
||||
});
|
||||
} catch (e) {}
|
||||
return n;
|
||||
}
|
||||
|
||||
applyGlobalTokens();
|
||||
let n = walk(document);
|
||||
console.log('[assist-fix] v5 initial ha-input styled:', n);
|
||||
|
||||
new MutationObserver(() => { applyGlobalTokens(); walk(document); })
|
||||
.observe(document.body || document.documentElement,
|
||||
{ childList: true, subtree: true });
|
||||
|
||||
window.addEventListener('show-dialog', () => {
|
||||
setTimeout(() => {
|
||||
const nn = walk(document);
|
||||
console.log('[assist-fix] v5 after show-dialog ha-input styled:', nn);
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// Re-run tokens whenever theme might change
|
||||
window.addEventListener('theme-changed', applyGlobalTokens);
|
||||
|
||||
let passes = 0;
|
||||
const iv = setInterval(() => {
|
||||
const nn = walk(document);
|
||||
if (nn > n) { console.log('[assist-fix] v5 now styled:', nn); n = nn; }
|
||||
if (++passes > 240) clearInterval(iv);
|
||||
}, 500);
|
||||
|
||||
console.log('[assist-fix] v5 loaded — wa-dark + wa-* tokens + inline');
|
||||
})();
|
||||
18
hosts/physical/concord-nuc/homeassistant/www/fonts-loader.js
Normal file
18
hosts/physical/concord-nuc/homeassistant/www/fonts-loader.js
Normal file
@@ -0,0 +1,18 @@
|
||||
(function() {
|
||||
if (document.getElementById('vish-themed-fonts')) return;
|
||||
var link = document.createElement('link');
|
||||
link.id = 'vish-themed-fonts';
|
||||
link.rel = 'stylesheet';
|
||||
link.href = 'https://fonts.googleapis.com/css2?' +
|
||||
'family=Exo+2:wght@300;400;500;600;700&' +
|
||||
'family=Rajdhani:wght@400;500;600;700&' +
|
||||
'family=Orbitron:wght@500;600;700;900&' +
|
||||
'family=Share+Tech+Mono&' +
|
||||
'family=Cormorant+Garamond:wght@400;500;600;700&' +
|
||||
'family=Playfair+Display:wght@500;600;700&' +
|
||||
'family=Crimson+Text:wght@400;600&' +
|
||||
'family=Noto+Serif+JP:wght@400;500;700&' +
|
||||
'family=Shippori+Mincho:wght@500;600;700&' +
|
||||
'family=Sawarabi+Mincho&display=swap';
|
||||
document.head.appendChild(link);
|
||||
})();
|
||||
13
hosts/physical/concord-nuc/invidious/docker/init-invidious-db.sh
Executable file
13
hosts/physical/concord-nuc/invidious/docker/init-invidious-db.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Invidious DB initialisation script
|
||||
# Runs once on first container start (docker-entrypoint-initdb.d).
|
||||
#
|
||||
# Adds a pg_hba.conf rule allowing connections from any Docker subnet
|
||||
# using trust auth. Without this, PostgreSQL rejects the invidious
|
||||
# container when the Docker network is assigned a different subnet after
|
||||
# a recreate (the default pg_hba.conf only covers localhost).
|
||||
|
||||
set -e
|
||||
|
||||
# Allow connections from any host on the Docker bridge network
|
||||
echo "host all all 0.0.0.0/0 trust" >> /var/lib/postgresql/data/pg_hba.conf
|
||||
115
hosts/physical/concord-nuc/invidious/invidious.yaml
Normal file
115
hosts/physical/concord-nuc/invidious/invidious.yaml
Normal file
@@ -0,0 +1,115 @@
|
||||
version: "3"
|
||||
|
||||
configs:
|
||||
materialious_nginx:
|
||||
content: |
|
||||
events { worker_connections 1024; }
|
||||
http {
|
||||
default_type application/octet-stream;
|
||||
include /etc/nginx/mime.types;
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
# The video player passes dashUrl as a relative path that resolves
|
||||
# to this origin — proxy Invidious API/media paths to local service.
|
||||
# (in.vish.gg resolves to the external IP which is unreachable via
|
||||
# hairpin NAT from inside Docker; invidious:3000 is on same network)
|
||||
location ~ ^/(api|companion|vi|ggpht|videoplayback|sb|s_p|ytc|storyboards) {
|
||||
proxy_pass http://invidious:3000;
|
||||
proxy_set_header Host $$host;
|
||||
proxy_set_header X-Real-IP $$remote_addr;
|
||||
proxy_set_header X-Forwarded-For $$proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $$uri /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
services:
|
||||
|
||||
invidious:
|
||||
image: quay.io/invidious/invidious:latest
|
||||
platform: linux/amd64
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
INVIDIOUS_CONFIG: |
|
||||
db:
|
||||
dbname: invidious
|
||||
user: kemal
|
||||
password: "REDACTED_PASSWORD"
|
||||
host: invidious-db
|
||||
port: 5432
|
||||
check_tables: true
|
||||
invidious_companion:
|
||||
- private_url: "http://companion:8282/companion"
|
||||
invidious_companion_key: "pha6nuser7ecei1E"
|
||||
hmac_key: "Kai5eexiewohchei"
|
||||
healthcheck:
|
||||
test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 2
|
||||
logging:
|
||||
options:
|
||||
max-size: "1G"
|
||||
max-file: "4"
|
||||
depends_on:
|
||||
- invidious-db
|
||||
- companion
|
||||
|
||||
companion:
|
||||
image: quay.io/invidious/invidious-companion:latest
|
||||
platform: linux/amd64
|
||||
environment:
|
||||
- SERVER_SECRET_KEY=pha6nuser7ecei1E
|
||||
restart: unless-stopped
|
||||
cap_drop:
|
||||
- ALL
|
||||
read_only: true
|
||||
volumes:
|
||||
- companioncache:/var/tmp/youtubei.js:rw
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
logging:
|
||||
options:
|
||||
max-size: "1G"
|
||||
max-file: "4"
|
||||
|
||||
invidious-db:
|
||||
image: postgres:14
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: invidious
|
||||
POSTGRES_USER: kemal
|
||||
POSTGRES_PASSWORD: "REDACTED_PASSWORD" # pragma: allowlist secret
|
||||
volumes:
|
||||
- postgresdata:/var/lib/postgresql/data
|
||||
- ./config/sql:/config/sql
|
||||
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
|
||||
materialious:
|
||||
image: wardpearce/materialious:latest
|
||||
container_name: materialious
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
VITE_DEFAULT_INVIDIOUS_INSTANCE: "https://in.vish.gg"
|
||||
configs:
|
||||
- source: materialious_nginx
|
||||
target: /etc/nginx/nginx.conf
|
||||
ports:
|
||||
- "3001:80"
|
||||
logging:
|
||||
options:
|
||||
max-size: "1G"
|
||||
max-file: "4"
|
||||
|
||||
volumes:
|
||||
postgresdata:
|
||||
companioncache:
|
||||
4
hosts/physical/concord-nuc/invidious/invidious_notes.txt
Normal file
4
hosts/physical/concord-nuc/invidious/invidious_notes.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
vish@vish-concord-nuc:~/invidious/invidious$ pwgen 16 1 # for Invidious (HMAC_KEY)
|
||||
Kai5eexiewohchei
|
||||
vish@vish-concord-nuc:~/invidious/invidious$ pwgen 16 1 # for Invidious companion (invidious_companion_key)
|
||||
pha6nuser7ecei1E
|
||||
@@ -0,0 +1,65 @@
|
||||
version: "3.8" # Upgrade to a newer version for better features and support
|
||||
|
||||
services:
|
||||
invidious:
|
||||
image: quay.io/invidious/invidious:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
INVIDIOUS_CONFIG: |
|
||||
db:
|
||||
dbname: invidious
|
||||
user: kemal
|
||||
password: "REDACTED_PASSWORD"
|
||||
host: invidious-db
|
||||
port: 5432
|
||||
check_tables: true
|
||||
signature_server: inv_sig_helper:12999
|
||||
visitor_data: ""
|
||||
po_token: "REDACTED_TOKEN"=="
|
||||
hmac_key: "9Uncxo4Ws54s7dr0i3t8"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-nv", "--tries=1", "--spider", "http://127.0.0.1:3000/api/v1/trending"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 2
|
||||
logging:
|
||||
options:
|
||||
max-size: "1G"
|
||||
max-file: "4"
|
||||
depends_on:
|
||||
- invidious-db
|
||||
|
||||
inv_sig_helper:
|
||||
image: quay.io/invidious/inv-sig-helper:latest
|
||||
init: true
|
||||
command: ["--tcp", "0.0.0.0:12999"]
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
restart: unless-stopped
|
||||
cap_drop:
|
||||
- ALL
|
||||
read_only: true
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
invidious-db:
|
||||
image: docker.io/library/postgres:14
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgresdata:/var/lib/postgresql/data
|
||||
- ./config/sql:/config/sql
|
||||
- ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
|
||||
environment:
|
||||
POSTGRES_DB: invidious
|
||||
POSTGRES_USER: kemal
|
||||
POSTGRES_PASSWORD: "REDACTED_PASSWORD"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
postgresdata:
|
||||
@@ -0,0 +1,2 @@
|
||||
docker all in one
|
||||
docker-compose down --volumes --remove-orphans && docker-compose pull && docker-compose up -d
|
||||
28
hosts/physical/concord-nuc/nginx/client.spotify.vish.gg.conf
Normal file
28
hosts/physical/concord-nuc/nginx/client.spotify.vish.gg.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
# Redirect all HTTP traffic to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name client.spotify.vish.gg;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS configuration for the subdomain
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name client.spotify.vish.gg;
|
||||
|
||||
# SSL Certificates (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/client.spotify.vish.gg/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/client.spotify.vish.gg/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
# Proxy to Docker container
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4000; # Maps to your Docker container
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
63
hosts/physical/concord-nuc/nginx/in.vish.gg.conf
Normal file
63
hosts/physical/concord-nuc/nginx/in.vish.gg.conf
Normal file
@@ -0,0 +1,63 @@
|
||||
server {
|
||||
if ($host = in.vish.gg) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name in.vish.gg;
|
||||
|
||||
# Redirect all HTTP traffic to HTTPS
|
||||
return 301 https://$host$request_uri;
|
||||
|
||||
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name in.vish.gg;
|
||||
|
||||
# SSL Certificates (Certbot paths)
|
||||
ssl_certificate /etc/letsencrypt/live/in.vish.gg/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/in.vish.gg/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# --- Reverse Proxy to Invidious ---
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# Required headers for reverse proxying
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# WebSocket and streaming stability
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Disable buffering for video streams
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
|
||||
# Avoid premature timeouts during long playback
|
||||
proxy_read_timeout 600s;
|
||||
proxy_send_timeout 600s;
|
||||
}
|
||||
|
||||
# Cache static assets (images, css, js) for better performance
|
||||
location ~* \.(?:jpg|jpeg|png|gif|ico|css|js|webp)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
}
|
||||
|
||||
# Security headers (optional but sensible)
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-Frame-Options SAMEORIGIN;
|
||||
add_header Referrer-Policy same-origin;
|
||||
|
||||
}
|
||||
28
hosts/physical/concord-nuc/nginx/spotify.conf
Normal file
28
hosts/physical/concord-nuc/nginx/spotify.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
# Redirect HTTP to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name spotify.vish.gg;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS server block
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name spotify.vish.gg;
|
||||
|
||||
# SSL Certificates (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/spotify.vish.gg/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/spotify.vish.gg/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Proxy requests to backend API
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:15000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
74
hosts/physical/concord-nuc/nginx/vp.vish.gg.conf
Normal file
74
hosts/physical/concord-nuc/nginx/vp.vish.gg.conf
Normal file
@@ -0,0 +1,74 @@
|
||||
# Redirect HTTP to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name vp.vish.gg api.vp.vish.gg proxy.vp.vish.gg;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS Reverse Proxy for Piped
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name vp.vish.gg;
|
||||
|
||||
# SSL Certificates (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/vp.vish.gg/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/vp.vish.gg/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Proxy requests to Piped Frontend (use Docker service name, NOT 127.0.0.1)
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS Reverse Proxy for Piped API
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name api.vp.vish.gg;
|
||||
|
||||
# SSL Certificates
|
||||
ssl_certificate /etc/letsencrypt/live/vp.vish.gg/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/vp.vish.gg/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Proxy requests to Piped API backend
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS Reverse Proxy for Piped Proxy (for video streaming)
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name proxy.vp.vish.gg;
|
||||
|
||||
# SSL Certificates
|
||||
ssl_certificate /etc/letsencrypt/live/vp.vish.gg/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/vp.vish.gg/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Proxy video playback requests through ytproxy
|
||||
location ~ (/videoplayback|/api/v4/|/api/manifest/) {
|
||||
include snippets/ytproxy.conf;
|
||||
add_header Cache-Control private always;
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
}
|
||||
|
||||
location / {
|
||||
include snippets/ytproxy.conf;
|
||||
add_header Cache-Control "public, max-age=604800";
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
}
|
||||
}
|
||||
24
hosts/physical/concord-nuc/node-exporter.yaml
Normal file
24
hosts/physical/concord-nuc/node-exporter.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# Node Exporter - Prometheus metrics exporter for hardware/OS metrics
|
||||
# Exposes metrics on port 9101 (changed from 9100 due to host conflict)
|
||||
# Used by: Grafana/Prometheus monitoring stack
|
||||
# Note: Using bridge network with port mapping instead of host network
|
||||
# to avoid conflict with host-installed node_exporter
|
||||
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
node-exporter:
|
||||
image: quay.io/prometheus/node-exporter:latest
|
||||
container_name: node_exporter
|
||||
ports:
|
||||
- "9101:9100"
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.sysfs=/host/sys'
|
||||
- '--path.rootfs=/rootfs'
|
||||
- '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'
|
||||
restart: unless-stopped
|
||||
79
hosts/physical/concord-nuc/piped.yaml
Normal file
79
hosts/physical/concord-nuc/piped.yaml
Normal file
@@ -0,0 +1,79 @@
|
||||
# Piped - YouTube frontend
|
||||
# Port: 8080
|
||||
# Privacy-respecting YouTube
|
||||
|
||||
services:
|
||||
piped-frontend:
|
||||
image: 1337kavin/piped-frontend:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- piped
|
||||
environment:
|
||||
BACKEND_HOSTNAME: api.vp.vish.gg
|
||||
HTTP_MODE: https
|
||||
container_name: piped-frontend
|
||||
piped-proxy:
|
||||
image: 1337kavin/piped-proxy:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- UDS=1
|
||||
volumes:
|
||||
- piped-proxy:/app/socket
|
||||
container_name: piped-proxy
|
||||
piped:
|
||||
image: 1337kavin/piped:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config/config.properties:/app/config.properties:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
container_name: piped-backend
|
||||
bg-helper:
|
||||
image: 1337kavin/bg-helper-server:latest
|
||||
restart: unless-stopped
|
||||
container_name: piped-bg-helper
|
||||
nginx:
|
||||
image: nginx:mainline-alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./config/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./config/pipedapi.conf:/etc/nginx/conf.d/pipedapi.conf:ro
|
||||
- ./config/pipedproxy.conf:/etc/nginx/conf.d/pipedproxy.conf:ro
|
||||
- ./config/pipedfrontend.conf:/etc/nginx/conf.d/pipedfrontend.conf:ro
|
||||
- ./config/ytproxy.conf:/etc/nginx/snippets/ytproxy.conf:ro
|
||||
- piped-proxy:/var/run/ytproxy
|
||||
container_name: nginx
|
||||
depends_on:
|
||||
- piped
|
||||
- piped-proxy
|
||||
- piped-frontend
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.piped.rule=Host(`FRONTEND_HOSTNAME`, `BACKEND_HOSTNAME`, `PROXY_HOSTNAME`)"
|
||||
- "traefik.http.routers.piped.entrypoints=websecure"
|
||||
- "traefik.http.services.piped.loadbalancer.server.port=8080"
|
||||
postgres:
|
||||
image: pgautoupgrade/pgautoupgrade:16-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data/db:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=piped
|
||||
- POSTGRES_USER=piped
|
||||
- POSTGRES_PASSWORD="REDACTED_PASSWORD"
|
||||
container_name: postgres
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
environment:
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
- WATCHTOWER_INCLUDE_RESTARTING=true
|
||||
container_name: watchtower
|
||||
command: piped-frontend piped-backend piped-proxy piped-bg-helper varnish nginx postgres watchtower
|
||||
volumes:
|
||||
piped-proxy: null
|
||||
28
hosts/physical/concord-nuc/plex.yaml
Normal file
28
hosts/physical/concord-nuc/plex.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# Plex Media Server
|
||||
# Web UI: http://<host-ip>:32400/web
|
||||
# Uses Intel QuickSync for hardware transcoding (via /dev/dri)
|
||||
# Media library mounted from NAS at /mnt/nas
|
||||
|
||||
services:
|
||||
plex:
|
||||
image: linuxserver/plex:latest
|
||||
container_name: plex
|
||||
network_mode: host
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- VERSION=docker
|
||||
# Get claim token from: https://www.plex.tv/claim/
|
||||
- PLEX_CLAIM=claim-REDACTED_APP_PASSWORD
|
||||
volumes:
|
||||
- /home/vish/docker/plex/config:/config
|
||||
- /mnt/nas/:/data/media
|
||||
devices:
|
||||
# Intel QuickSync for hardware transcoding
|
||||
- /dev/dri:/dev/dri
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: on-failure:10
|
||||
# custom-cont-init.d/01-wait-for-nas.sh waits up to 120s for /mnt/nas before starting Plex
|
||||
22
hosts/physical/concord-nuc/portainer_agent.yaml
Normal file
22
hosts/physical/concord-nuc/portainer_agent.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# Portainer Edge Agent - concord-nuc
|
||||
# Connects to Portainer server on Atlantis (100.83.230.112:8000)
|
||||
# Deploy: docker compose -f portainer_agent.yaml up -d
|
||||
|
||||
services:
|
||||
portainer_edge_agent:
|
||||
image: portainer/agent:2.33.7
|
||||
container_name: portainer_edge_agent
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/lib/docker/volumes:/var/lib/docker/volumes
|
||||
- /:/host
|
||||
- portainer_agent_data:/data
|
||||
environment:
|
||||
EDGE: "1"
|
||||
EDGE_ID: "be02f203-f10c-471a-927c-9ca2adac254c"
|
||||
EDGE_KEY: "aHR0cDovLzEwMC44My4yMzAuMTEyOjEwMDAwfGh0dHA6Ly8xMDAuODMuMjMwLjExMjo4MDAwfGtDWjVkTjJyNXNnQTJvMEF6UDN4R3h6enBpclFqa05Wa0FCQkU0R1IxWFU9fDQ0MzM5OA"
|
||||
EDGE_INSECURE_POLL: "1"
|
||||
|
||||
volumes:
|
||||
portainer_agent_data:
|
||||
22
hosts/physical/concord-nuc/scrutiny-collector.yaml
Normal file
22
hosts/physical/concord-nuc/scrutiny-collector.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
# Scrutiny Collector — concord-nuc (Intel NUC)
|
||||
#
|
||||
# Ships SMART data to the hub on homelab-vm.
|
||||
# NUC typically has one internal NVMe + optionally a SATA SSD.
|
||||
# Adjust device list: run `lsblk` to see actual drives.
|
||||
#
|
||||
# Hub: http://100.67.40.126:8090
|
||||
|
||||
services:
|
||||
scrutiny-collector:
|
||||
image: ghcr.io/analogj/scrutiny:master-collector
|
||||
container_name: scrutiny-collector
|
||||
cap_add:
|
||||
- SYS_RAWIO
|
||||
- SYS_ADMIN
|
||||
volumes:
|
||||
- /run/udev:/run/udev:ro
|
||||
devices:
|
||||
- /dev/sda
|
||||
environment:
|
||||
COLLECTOR_API_ENDPOINT: "http://100.67.40.126:8090"
|
||||
restart: unless-stopped
|
||||
19
hosts/physical/concord-nuc/syncthing.yaml
Normal file
19
hosts/physical/concord-nuc/syncthing.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
# Syncthing - File synchronization
|
||||
# Port: 8384 (web), 22000 (sync)
|
||||
# Continuous file synchronization between devices
|
||||
services:
|
||||
syncthing:
|
||||
container_name: syncthing
|
||||
ports:
|
||||
- 8384:8384
|
||||
- 22000:22000/tcp
|
||||
- 22000:22000/udp
|
||||
- 21027:21027/udp
|
||||
environment:
|
||||
- TZ=America/Los_Angeles
|
||||
volumes:
|
||||
- /home/vish/docker/syncthing/config:/config
|
||||
- /home/vish/docker/syncthing/data1:/data1
|
||||
- /home/vish/docker/syncthing/data2:/data2
|
||||
restart: unless-stopped
|
||||
image: ghcr.io/linuxserver/syncthing
|
||||
25
hosts/physical/concord-nuc/wireguard.yaml
Normal file
25
hosts/physical/concord-nuc/wireguard.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# WireGuard - VPN server
|
||||
# Port: 51820/udp
|
||||
# Modern, fast VPN tunnel
|
||||
services:
|
||||
wg-easy:
|
||||
container_name: wg-easy
|
||||
image: ghcr.io/wg-easy/wg-easy
|
||||
|
||||
environment:
|
||||
- HASH_PASSWORD="REDACTED_PASSWORD"
|
||||
- WG_HOST=vishconcord.tplinkdns.com
|
||||
|
||||
volumes:
|
||||
- ./config:/etc/wireguard
|
||||
- /lib/modules:/lib/modules
|
||||
ports:
|
||||
- "51820:51820/udp"
|
||||
- "51821:51821/tcp"
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- SYS_MODULE
|
||||
sysctls:
|
||||
- net.ipv4.ip_forward=1
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
49
hosts/physical/concord-nuc/yourspotify.yaml
Normal file
49
hosts/physical/concord-nuc/yourspotify.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
# Your Spotify - Listening statistics
|
||||
# Port: 3000
|
||||
# Self-hosted Spotify listening history tracker
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
server:
|
||||
image: yooooomi/your_spotify_server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "15000:8080" # Expose port 15000 for backend service
|
||||
depends_on:
|
||||
- mongo
|
||||
environment:
|
||||
- API_ENDPOINT=https://spotify.vish.gg # Public URL for backend
|
||||
- CLIENT_ENDPOINT=https://spotify-client.vish.gg # Public URL for frontend
|
||||
- SPOTIFY_PUBLIC=d6b3bda999f042099ce79a8b6e9f9e68 # Spotify app client ID
|
||||
- SPOTIFY_SECRET=72c650e7a25f441baa245b963003a672 # Spotify app client secret
|
||||
- SPOTIFY_REDIRECT_URI=https://spotify-client.vish.gg/callback # Redirect URI for OAuth
|
||||
- CORS=https://spotify-client.vish.gg # Allow frontend's origin
|
||||
networks:
|
||||
- spotify_network
|
||||
|
||||
mongo:
|
||||
container_name: mongo
|
||||
image: mongo:4.4.8
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- yourspotify_mongo_data:/data/db # Named volume for persistent storage
|
||||
networks:
|
||||
- spotify_network
|
||||
|
||||
web:
|
||||
image: yooooomi/your_spotify_client
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "4000:3000" # Expose port 4000 for frontend
|
||||
environment:
|
||||
- API_ENDPOINT=https://spotify.vish.gg # URL for backend API
|
||||
networks:
|
||||
- spotify_network
|
||||
|
||||
volumes:
|
||||
yourspotify_mongo_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
spotify_network:
|
||||
driver: bridge
|
||||
234
hosts/physical/guava/README.md
Normal file
234
hosts/physical/guava/README.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Guava - TrueNAS Scale Server
|
||||
|
||||
**Hostname**: guava
|
||||
**IP Address**: 192.168.0.100
|
||||
**Tailscale IP**: 100.75.252.64
|
||||
**Domain**: guava.crista.home
|
||||
**OS**: TrueNAS Scale 25.04.2.6 (Debian 12 Bookworm)
|
||||
**Kernel**: 6.12.15-production+truenas
|
||||
|
||||
---
|
||||
|
||||
## Hardware Specifications
|
||||
|
||||
| Component | Specification |
|
||||
|-----------|---------------|
|
||||
| **CPU** | 12 cores |
|
||||
| **RAM** | 30 GB |
|
||||
| **Storage** | ZFS pools (1.5TB+ available) |
|
||||
| **Docker** | 27.5.0 |
|
||||
| **Compose** | v2.32.3 |
|
||||
|
||||
---
|
||||
|
||||
## Storage Layout
|
||||
|
||||
### Boot Pool
|
||||
- `/` - Root filesystem (433GB available)
|
||||
- ZFS dataset: `boot-pool/ROOT/25.04.2.6`
|
||||
|
||||
### Data Pool (`/mnt/data/`)
|
||||
| Dataset | Size Used | Purpose |
|
||||
|---------|-----------|---------|
|
||||
| `data/guava_turquoise` | 3.0TB / 4.5TB | Primary storage (67% used) |
|
||||
| `data/photos` | 159GB | Photo storage |
|
||||
| `data/jellyfin` | 145GB | Media library |
|
||||
| `data/llama` | 59GB | LLM models |
|
||||
| `data/plane-data` | ~100MB | Plane.so application data |
|
||||
| `data/iso` | 556MB | ISO images |
|
||||
| `data/cocalc` | 324MB | Computational notebook |
|
||||
| `data/website` | 59MB | Web content |
|
||||
| `data/openproject` | 13MB | OpenProject (postgres) |
|
||||
| `data/fasten` | 5.7MB | Health records |
|
||||
| `data/fenrus` | 3.5MB | Dashboard config |
|
||||
| `data/medical` | 14MB | Medical records |
|
||||
| `data/truenas-exporters` | - | Prometheus exporters |
|
||||
|
||||
### TrueNAS Apps (`/mnt/.ix-apps/`)
|
||||
- Docker storage: 28GB used
|
||||
- App configs and mounts for TrueNAS-managed apps
|
||||
|
||||
---
|
||||
|
||||
## Network Configuration
|
||||
|
||||
| Service | Port | Protocol | URL |
|
||||
|---------|------|----------|-----|
|
||||
| Portainer | 31015 | HTTPS | https://guava.crista.home:31015 |
|
||||
| **Plane.so** | 3080 | HTTP | **http://guava.crista.home:3080** |
|
||||
| Plane.so HTTPS | 3443 | HTTPS | https://guava.crista.home:3443 |
|
||||
| Jellyfin | 30013 | HTTP | http://guava.crista.home:30013 |
|
||||
| Jellyfin HTTPS | 30014 | HTTPS | https://guava.crista.home:30014 |
|
||||
| Gitea | 30008-30009 | HTTP | http://guava.crista.home:30008 |
|
||||
| WireGuard | 51827 | UDP | - |
|
||||
| wg-easy UI | 30058 | HTTP | http://guava.crista.home:30058 |
|
||||
| Fenrus | 45678 | HTTP | http://guava.crista.home:45678 |
|
||||
| Fasten | 9090 | HTTP | http://guava.crista.home:9090 |
|
||||
| Node Exporter | 9100 | HTTP | http://guava.crista.home:9100/metrics |
|
||||
| nginx | 28888 | HTTP | http://guava.crista.home:28888 |
|
||||
| iperf3 | 5201 | TCP | - |
|
||||
| SSH | 22 | TCP | - |
|
||||
| SMB | 445 | TCP | - |
|
||||
| Pi-hole DNS | 53 | TCP/UDP | - |
|
||||
|
||||
---
|
||||
|
||||
## Portainer Access
|
||||
|
||||
| Setting | Value |
|
||||
|---------|-------|
|
||||
| **URL** | `https://guava.crista.home:31015` |
|
||||
| **API Endpoint** | `https://localhost:31015/api` (from guava) |
|
||||
| **Endpoint ID** | 3 (local) |
|
||||
| **API Token** | `ptr_REDACTED_PORTAINER_TOKEN` |
|
||||
|
||||
### API Examples
|
||||
|
||||
```bash
|
||||
# List stacks
|
||||
curl -sk -H 'X-API-Key: "REDACTED_API_KEY" \
|
||||
'https://localhost:31015/api/stacks'
|
||||
|
||||
# List containers
|
||||
curl -sk -H 'X-API-Key: "REDACTED_API_KEY" \
|
||||
'https://localhost:31015/api/endpoints/3/docker/containers/json'
|
||||
|
||||
# Create stack from compose string
|
||||
curl -sk -X POST \
|
||||
-H 'X-API-Key: "REDACTED_API_KEY" \
|
||||
-H 'Content-Type: application/json' \
|
||||
'https://localhost:31015/api/stacks/create/standalone/string?endpointId=3' \
|
||||
-d '{"name": "my-stack", "REDACTED_APP_PASSWORD": "..."}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployed Stacks (Portainer)
|
||||
|
||||
| ID | Name | Status | Description |
|
||||
|----|------|--------|-------------|
|
||||
| 2 | nginx | ✅ Active | Reverse proxy (:28888) |
|
||||
| 3 | ddns | ✅ Active | Dynamic DNS updater (crista.love) |
|
||||
| 4 | llama | ⏸️ Inactive | LLM server |
|
||||
| 5 | fenrus | ✅ Active | Dashboard (:45678) |
|
||||
| 8 | fasten | ✅ Active | Health records (:9090) |
|
||||
| 17 | node-exporter | ✅ Active | Prometheus metrics (:9100) |
|
||||
| 18 | iperf3 | ✅ Active | Network speed testing (:5201) |
|
||||
| 25 | cocalc | ⏸️ Inactive | Computational notebook |
|
||||
| **26** | **plane-stack** | ✅ Active | **Project management (:3080)** |
|
||||
|
||||
### TrueNAS-Managed Apps (ix-apps)
|
||||
| App | Container | Port | Description |
|
||||
|-----|-----------|------|-------------|
|
||||
| Portainer | ix-portainer-portainer-1 | 31015 | Container management |
|
||||
| Gitea | ix-gitea-gitea-1 | 30008-30009 | Git server |
|
||||
| Gitea DB | ix-gitea-postgres-1 | - | PostgreSQL for Gitea |
|
||||
| Jellyfin | ix-jellyfin-jellyfin-1 | 30013, 30014 | Media server |
|
||||
| WireGuard | ix-wg-easy-wg-easy-1 | 30058, 51827/udp | VPN server |
|
||||
| Tailscale | ix-tailscale-tailscale-1 | - | Mesh VPN |
|
||||
| Pi-hole | (configured) | 53 | DNS server |
|
||||
|
||||
---
|
||||
|
||||
## SSH Access
|
||||
|
||||
### Via Cloudflare Tunnel
|
||||
|
||||
```bash
|
||||
# Install cloudflared
|
||||
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /tmp/cloudflared
|
||||
chmod +x /tmp/cloudflared
|
||||
|
||||
# SSH config
|
||||
cat >> ~/.ssh/config << 'EOF'
|
||||
Host guava
|
||||
HostName ruled-bowl-dos-jews.trycloudflare.com
|
||||
User vish
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
ProxyCommand /tmp/cloudflared access ssh --hostname %h
|
||||
EOF
|
||||
|
||||
# Connect
|
||||
ssh guava
|
||||
```
|
||||
|
||||
### Direct (Local Network)
|
||||
|
||||
```bash
|
||||
ssh vish@192.168.0.100
|
||||
```
|
||||
|
||||
**Note**: Docker commands require `sudo` on guava.
|
||||
|
||||
---
|
||||
|
||||
## Services Documentation
|
||||
|
||||
### Plane.so
|
||||
|
||||
See [plane.yaml](plane.yaml) for the full stack configuration.
|
||||
|
||||
| Component | Container | Port | Purpose |
|
||||
|-----------|-----------|------|---------|
|
||||
| Frontend | plane-web | 3000 | Web UI |
|
||||
| Admin | plane-admin | 3000 | Admin panel |
|
||||
| Space | plane-space | 3000 | Public pages |
|
||||
| API | plane-api | 8000 | Backend API |
|
||||
| Worker | plane-worker | 8000 | Background jobs |
|
||||
| Beat | plane-beat | 8000 | Scheduled tasks |
|
||||
| Live | plane-live | 3000 | Real-time updates |
|
||||
| Database | plane-db | 5432 | PostgreSQL |
|
||||
| Cache | plane-redis | 6379 | Valkey/Redis |
|
||||
| Queue | plane-mq | 5672 | RabbitMQ |
|
||||
| Storage | plane-minio | 9000 | MinIO S3 |
|
||||
| Proxy | plane-proxy | 80/443 | Caddy reverse proxy |
|
||||
|
||||
**Access URL**: http://guava.crista.home:3080
|
||||
|
||||
**Data Location**: `/mnt/data/plane-data/`
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Backup Locations
|
||||
|
||||
| Data | Path | Priority |
|
||||
|------|------|----------|
|
||||
| Plane DB | `/mnt/data/plane-data/postgres/` | High |
|
||||
| Plane Files | `/mnt/data/plane-data/minio/` | High |
|
||||
| Gitea | `/mnt/.ix-apps/app_mounts/gitea/` | High |
|
||||
| Jellyfin Config | `/mnt/.ix-apps/app_mounts/jellyfin/config/` | Medium |
|
||||
| Photos | `/mnt/data/photos/` | High |
|
||||
|
||||
### Common Commands
|
||||
|
||||
```bash
|
||||
# Check all containers
|
||||
sudo docker ps -a
|
||||
|
||||
# View stack logs
|
||||
sudo docker compose -f /path/to/stack logs -f
|
||||
|
||||
# Restart a stack via Portainer API
|
||||
curl -sk -X POST \
|
||||
-H 'X-API-Key: TOKEN' \
|
||||
'https://localhost:31015/api/stacks/STACK_ID/stop?endpointId=3'
|
||||
|
||||
curl -sk -X POST \
|
||||
-H 'X-API-Key: TOKEN' \
|
||||
'https://localhost:31015/api/stacks/STACK_ID/start?endpointId=3'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Plane.so Service Docs](../../../docs/services/individual/plane.md)
|
||||
- [TrueNAS Scale Documentation](https://www.truenas.com/docs/scale/)
|
||||
- [AGENTS.md](../../../AGENTS.md) - Quick reference for all hosts
|
||||
|
||||
---
|
||||
|
||||
*Last updated: February 4, 2026*
|
||||
*Verified via SSH - all services confirmed running*
|
||||
23
hosts/physical/guava/guava_info.txt
Normal file
23
hosts/physical/guava/guava_info.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Guava CIFS/SMB Shares
|
||||
|
||||
data /mnt/data/passionfruit
|
||||
guava_turquoise /mnt/data/guava_turquoise Backup of turquoise
|
||||
photos /mnt/data/photos
|
||||
|
||||
|
||||
Global Configuration
|
||||
Nameservers
|
||||
Nameserver 1:
|
||||
1.1.1.1
|
||||
Nameserver 2:
|
||||
192.168.0.250
|
||||
Default Route
|
||||
IPv4:
|
||||
192.168.0.1
|
||||
Hostname:guava
|
||||
Domain: local
|
||||
HTTP Proxy:---
|
||||
Service Announcement: NETBIOS-NS, mDNS, WS-DISCOVERY
|
||||
Additional Domains:---
|
||||
Hostname Database:---
|
||||
Outbound Network:Allow All
|
||||
213
hosts/physical/guava/plane.yaml
Normal file
213
hosts/physical/guava/plane.yaml
Normal file
@@ -0,0 +1,213 @@
|
||||
# Plane.so - Self-Hosted Project Management
|
||||
# Deployed via Portainer on TrueNAS Scale (guava)
|
||||
# Port: 3080 (HTTP), 3443 (HTTPS)
|
||||
|
||||
x-db-env: &db-env
|
||||
PGHOST: plane-db
|
||||
PGDATABASE: plane
|
||||
POSTGRES_USER: plane
|
||||
POSTGRES_PASSWORD: "REDACTED_PASSWORD"
|
||||
POSTGRES_DB: plane
|
||||
POSTGRES_PORT: 5432
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
|
||||
x-redis-env: &redis-env
|
||||
REDIS_HOST: plane-redis
|
||||
REDIS_PORT: 6379
|
||||
REDIS_URL: redis://plane-redis:6379/
|
||||
|
||||
x-minio-env: &minio-env
|
||||
MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID:-planeaccess}
|
||||
MINIO_ROOT_PASSWORD: "REDACTED_PASSWORD"
|
||||
|
||||
x-aws-s3-env: &aws-s3-env
|
||||
AWS_REGION: us-east-1
|
||||
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-planeaccess}
|
||||
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-planesecret123}
|
||||
AWS_S3_ENDPOINT_URL: http://plane-minio:9000
|
||||
AWS_S3_BUCKET_NAME: uploads
|
||||
|
||||
x-proxy-env: &proxy-env
|
||||
APP_DOMAIN: ${APP_DOMAIN:-guava.crista.home}
|
||||
FILE_SIZE_LIMIT: 52428800
|
||||
LISTEN_HTTP_PORT: 80
|
||||
LISTEN_HTTPS_PORT: 443
|
||||
BUCKET_NAME: uploads
|
||||
SITE_ADDRESS: :80
|
||||
|
||||
x-mq-env: &mq-env
|
||||
RABBITMQ_HOST: plane-mq
|
||||
RABBITMQ_PORT: 5672
|
||||
RABBITMQ_DEFAULT_USER: plane
|
||||
RABBITMQ_DEFAULT_PASS: "REDACTED_PASSWORD"REDACTED_PASSWORD"
|
||||
RABBITMQ_DEFAULT_VHOST: plane
|
||||
RABBITMQ_VHOST: plane
|
||||
|
||||
x-live-env: &live-env
|
||||
API_BASE_URL: http://api:8000
|
||||
LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
|
||||
x-app-env: &app-env
|
||||
WEB_URL: ${WEB_URL:-http://guava.crista.home:3080}
|
||||
DEBUG: 0
|
||||
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-}
|
||||
GUNICORN_WORKERS: 2
|
||||
USE_MINIO: 1
|
||||
DATABASE_URL: postgresql://plane:${POSTGRES_PASSWORD:"REDACTED_PASSWORD"
|
||||
SECRET_KEY: ${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
AMQP_URL: amqp://plane:${RABBITMQ_PASSWORD:"REDACTED_PASSWORD"
|
||||
API_KEY_RATE_LIMIT: 60/minute
|
||||
MINIO_ENDPOINT_SSL: 0
|
||||
LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
|
||||
|
||||
services:
|
||||
web:
|
||||
image: artifacts.plane.so/makeplane/plane-frontend:stable
|
||||
container_name: plane-web
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
|
||||
space:
|
||||
image: artifacts.plane.so/makeplane/plane-space:stable
|
||||
container_name: plane-space
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- api
|
||||
- worker
|
||||
- web
|
||||
|
||||
admin:
|
||||
image: artifacts.plane.so/makeplane/plane-admin:stable
|
||||
container_name: plane-admin
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- api
|
||||
- web
|
||||
|
||||
live:
|
||||
image: artifacts.plane.so/makeplane/plane-live:stable
|
||||
container_name: plane-live
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: [*live-env, *redis-env]
|
||||
depends_on:
|
||||
- api
|
||||
- web
|
||||
|
||||
api:
|
||||
image: artifacts.plane.so/makeplane/plane-backend:stable
|
||||
container_name: plane-api
|
||||
command: ./bin/docker-entrypoint-api.sh
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env]
|
||||
depends_on:
|
||||
plane-db:
|
||||
condition: service_healthy
|
||||
plane-redis:
|
||||
condition: service_started
|
||||
plane-mq:
|
||||
condition: service_started
|
||||
|
||||
worker:
|
||||
image: artifacts.plane.so/makeplane/plane-backend:stable
|
||||
container_name: plane-worker
|
||||
command: ./bin/docker-entrypoint-worker.sh
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env]
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
- plane-mq
|
||||
|
||||
beat-worker:
|
||||
image: artifacts.plane.so/makeplane/plane-backend:stable
|
||||
container_name: plane-beat
|
||||
command: ./bin/docker-entrypoint-beat.sh
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env]
|
||||
depends_on:
|
||||
- api
|
||||
- plane-db
|
||||
- plane-redis
|
||||
- plane-mq
|
||||
|
||||
migrator:
|
||||
image: artifacts.plane.so/makeplane/plane-backend:stable
|
||||
container_name: plane-migrator
|
||||
command: ./bin/docker-entrypoint-migrator.sh
|
||||
restart: on-failure
|
||||
environment:
|
||||
<<: [*app-env, *db-env, *redis-env, *minio-env, *aws-s3-env, *proxy-env]
|
||||
depends_on:
|
||||
plane-db:
|
||||
condition: service_healthy
|
||||
plane-redis:
|
||||
condition: service_started
|
||||
|
||||
plane-db:
|
||||
image: postgres:15.7-alpine
|
||||
container_name: plane-db
|
||||
command: postgres -c 'max_connections=1000'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: *db-env
|
||||
volumes:
|
||||
- /mnt/data/plane-data/postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U plane -d plane"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
plane-redis:
|
||||
image: valkey/valkey:7.2.11-alpine
|
||||
container_name: plane-redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/data/plane-data/redis:/data
|
||||
|
||||
plane-mq:
|
||||
image: rabbitmq:3.13.6-management-alpine
|
||||
container_name: plane-mq
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: *mq-env
|
||||
volumes:
|
||||
- /mnt/data/plane-data/rabbitmq:/var/lib/rabbitmq
|
||||
|
||||
plane-minio:
|
||||
image: minio/minio:latest
|
||||
container_name: plane-minio
|
||||
command: server /export --console-address ":9090"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: *minio-env
|
||||
volumes:
|
||||
- /mnt/data/plane-data/minio:/export
|
||||
|
||||
proxy:
|
||||
image: artifacts.plane.so/makeplane/plane-proxy:stable
|
||||
container_name: plane-proxy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
<<: *proxy-env
|
||||
ports:
|
||||
- "3080:80"
|
||||
- "3443:443"
|
||||
depends_on:
|
||||
- web
|
||||
- api
|
||||
- space
|
||||
- admin
|
||||
- live
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: plane-network
|
||||
driver: bridge
|
||||
25
hosts/physical/guava/portainer_yaml/cocalc.yaml
Normal file
25
hosts/physical/guava/portainer_yaml/cocalc.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
cocalc:
|
||||
image: sagemathinc/cocalc-docker:latest
|
||||
container_name: cocalc
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "8080:443" # expose CoCalc HTTPS on port 8080
|
||||
# or "443:443" if you want it directly bound to 443
|
||||
|
||||
volumes:
|
||||
# Persistent project and home directories
|
||||
- /mnt/data/cocalc/projects:/projects
|
||||
- /mnt/data/cocalc/home:/home/cocalc
|
||||
|
||||
# Optional: shared local "library of documents"
|
||||
- /mnt/data/cocalc/library:/projects/library
|
||||
|
||||
environment:
|
||||
- TZ=America/Los_Angeles
|
||||
- COCALC_NATS_AUTH=false # disable NATS auth for standalone use
|
||||
# - COCALC_ADMIN_PASSWORD="REDACTED_PASSWORD" # optional admin password
|
||||
# - COCALC_NO_IDLE_TIMEOUT=true # optional: stop idle shutdowns
|
||||
18
hosts/physical/guava/portainer_yaml/dynamic_dns.yaml
Normal file
18
hosts/physical/guava/portainer_yaml/dynamic_dns.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
ddns-crista-love:
|
||||
image: favonia/cloudflare-ddns:latest
|
||||
container_name: ddns-crista-love
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
user: "3000:3000"
|
||||
read_only: true
|
||||
cap_drop:
|
||||
- all
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
environment:
|
||||
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
|
||||
- DOMAINS=crista.love,cle.crista.love,cocalc.crista.love,mm.crista.love
|
||||
- PROXIED=true
|
||||
12
hosts/physical/guava/portainer_yaml/fasten_health.yaml
Normal file
12
hosts/physical/guava/portainer_yaml/fasten_health.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
fasten:
|
||||
image: ghcr.io/fastenhealth/fasten-onprem:main
|
||||
container_name: fasten-onprem
|
||||
ports:
|
||||
- "9090:8080"
|
||||
volumes:
|
||||
- /mnt/data/fasten/db:/opt/fasten/db
|
||||
- /mnt/data/fasten/cache:/opt/fasten/cache
|
||||
restart: unless-stopped
|
||||
19
hosts/physical/guava/portainer_yaml/fenrus_dashboard.yaml
Normal file
19
hosts/physical/guava/portainer_yaml/fenrus_dashboard.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
fenrus:
|
||||
image: revenz/fenrus:latest
|
||||
container_name: fenrus
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://127.0.0.1:3000/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
ports:
|
||||
- "45678:3000"
|
||||
volumes:
|
||||
- /mnt/data/fenrus:/app/data:rw
|
||||
environment:
|
||||
TZ: America/Los_Angeles
|
||||
restart: unless-stopped
|
||||
41
hosts/physical/guava/portainer_yaml/llama_gpt.yaml
Normal file
41
hosts/physical/guava/portainer_yaml/llama_gpt.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
ollama:
|
||||
image: ollama/ollama:latest
|
||||
container_name: ollama
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "11434:11434"
|
||||
environment:
|
||||
- OLLAMA_KEEP_ALIVE=10m
|
||||
volumes:
|
||||
- /mnt/data/llama:/root/.ollama
|
||||
# --- Optional AMD iGPU offload (experimental on SCALE) ---
|
||||
# devices:
|
||||
# - /dev/kfd
|
||||
# - /dev/dri
|
||||
# group_add:
|
||||
# - "video"
|
||||
# - "render"
|
||||
# environment:
|
||||
# - OLLAMA_KEEP_ALIVE=10m
|
||||
# - HSA_ENABLE_SDMA=0
|
||||
# - HSA_OVERRIDE_GFX_VERSION=11.0.0
|
||||
|
||||
openwebui:
|
||||
image: ghcr.io/open-webui/open-webui:latest
|
||||
container_name: open-webui
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- ollama
|
||||
ports:
|
||||
- "3000:8080" # browse to http://<truenas-ip>:3000
|
||||
environment:
|
||||
# Either var works on recent builds; keeping both for compatibility
|
||||
- OLLAMA_API_BASE_URL=http://ollama:11434
|
||||
- OLLAMA_BASE_URL=http://ollama:11434
|
||||
# Set to "false" to allow open signup without password
|
||||
- WEBUI_AUTH=true
|
||||
volumes:
|
||||
- /mnt/data/llama/open-webui:/app/backend/data
|
||||
10
hosts/physical/guava/portainer_yaml/llama_info.txt
Normal file
10
hosts/physical/guava/portainer_yaml/llama_info.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
My recommended use on your setup:
|
||||
Model Use case
|
||||
Llama3.1:8b Main general-purpose assistant
|
||||
Mistral:7b Fast, concise replies & RAG
|
||||
Qwen2.5:3b Lightweight, quick lookups
|
||||
Qwen2.5-Coder:7b Dedicated coding tasks
|
||||
Llama3:8b Legacy/benchmark (optional)
|
||||
qwen2.5:7b-instruct Writing up emails
|
||||
deepseek-r1 (chonky but accurate)
|
||||
deepseek-r1:8b (lighter version of r1 , can run on DS1823xs+)
|
||||
18
hosts/physical/guava/portainer_yaml/nginx.yaml
Normal file
18
hosts/physical/guava/portainer_yaml/nginx.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
container_name: nginx
|
||||
volumes:
|
||||
- /mnt/data/website/html:/usr/share/nginx/html:ro
|
||||
- /mnt/data/website/conf.d:/etc/nginx/conf.d:ro
|
||||
ports:
|
||||
- "28888:80" # 👈 Expose port 28888 on the host
|
||||
networks:
|
||||
- web-net
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
web-net:
|
||||
external: true
|
||||
18
hosts/physical/guava/portainer_yaml/node_exporter.yaml
Normal file
18
hosts/physical/guava/portainer_yaml/node_exporter.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
node-exporter:
|
||||
image: prom/node-exporter:latest
|
||||
container_name: node-exporter
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
pid: "host"
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.sysfs=/host/sys'
|
||||
- '--path.rootfs=/rootfs'
|
||||
- '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'
|
||||
Reference in New Issue
Block a user