Sanitized mirror from private repository - 2026-04-19 09:37:42 UTC
This commit is contained in:
19
hosts/edge/msi_laptop/openhands/docker-run.txt
Normal file
19
hosts/edge/msi_laptop/openhands/docker-run.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
services:
|
||||
openhands-app:
|
||||
image: docker.openhands.dev/openhands/openhands:0.62
|
||||
container_name: openhands-app
|
||||
environment:
|
||||
- OPENHANDS_LLM_PROVIDER=openai
|
||||
- OPENHANDS_LLM_MODEL=mistralai/devstral-small-2507
|
||||
- OPENHANDS_LLM_API_BASE=http://192.168.0.253:1234/v1
|
||||
- OPENHANDS_LLM_API_KEY=dummy
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.openhands.dev/openhands/runtime:0.62-nikolaik
|
||||
- LOG_ALL_EVENTS=true
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ~/openhands_clean:/.openhands
|
||||
ports:
|
||||
- 3000:3000
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
488
hosts/edge/nvidia_shield/README.md
Normal file
488
hosts/edge/nvidia_shield/README.md
Normal file
@@ -0,0 +1,488 @@
|
||||
# 🎮 NVIDIA Shield TV Pro 4K - Travel Device Configuration
|
||||
|
||||
**🟢 Beginner to Intermediate Guide**
|
||||
|
||||
The NVIDIA Shield TV Pro serves as a portable homelab access point, providing secure connectivity to your infrastructure while traveling. This guide covers setup, configuration, and usage scenarios.
|
||||
|
||||
## 📱 Device Overview
|
||||
|
||||
### **Hardware Specifications**
|
||||
- **Model**: NVIDIA Shield TV Pro (2019)
|
||||
- **CPU**: NVIDIA Tegra X1+ (8-core, 64-bit ARM)
|
||||
- **GPU**: 256-core NVIDIA GPU
|
||||
- **RAM**: 3GB LPDDR4
|
||||
- **Storage**: 16GB eMMC + microSD expansion
|
||||
- **Network**: Gigabit Ethernet + 802.11ac WiFi
|
||||
- **Ports**: 2x USB 3.0, HDMI 2.0b, microSD slot
|
||||
- **Power**: 20W external adapter
|
||||
- **Remote**: Voice remote with backlit buttons
|
||||
- **AI Upscaling**: NVIDIA AI upscaling to 4K
|
||||
|
||||
### **Travel Use Cases**
|
||||
| Scenario | Primary Function | Homelab Integration |
|
||||
|----------|------------------|-------------------|
|
||||
| **Hotel Room** | Media streaming, secure browsing | Plex/Jellyfin via Tailscale |
|
||||
| **Airbnb/Rental** | Personal entertainment system | Full homelab access |
|
||||
| **Family Visits** | Share media with family | Stream personal library |
|
||||
| **Business Travel** | Secure work environment | VPN gateway to homelab |
|
||||
| **Extended Travel** | Portable home setup | Complete service access |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Initial Setup & Configuration
|
||||
|
||||
### **Step 1: Basic Android TV Setup**
|
||||
```bash
|
||||
# Initial device setup
|
||||
1. Connect to power and HDMI display
|
||||
2. Follow Android TV setup wizard
|
||||
3. Sign in with Google account
|
||||
4. Connect to WiFi network
|
||||
5. Complete initial updates
|
||||
6. Enable Developer Options:
|
||||
- Settings > Device Preferences > About
|
||||
- Click "Build" 7 times to enable Developer Options
|
||||
- Settings > Device Preferences > Developer Options
|
||||
- Enable "USB Debugging"
|
||||
```
|
||||
|
||||
### **Step 2: Enable Sideloading**
|
||||
```bash
|
||||
# Allow installation of non-Play Store apps
|
||||
1. Settings > Device Preferences > Security & Restrictions
|
||||
2. Enable "Unknown Sources" for apps you trust
|
||||
3. Or enable per-app when installing Tailscale
|
||||
```
|
||||
|
||||
### **Step 3: Install Essential Apps**
|
||||
```bash
|
||||
# Core applications for homelab integration
|
||||
1. Tailscale (sideloaded)
|
||||
2. Plex (Play Store)
|
||||
3. VLC Media Player (Play Store)
|
||||
4. Chrome Browser (Play Store)
|
||||
5. Termux (Play Store) - for SSH access
|
||||
6. Solid Explorer (Play Store) - file management
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Tailscale Configuration
|
||||
|
||||
### **Installation Process**
|
||||
```bash
|
||||
# Method 1: Direct APK Installation (Recommended)
|
||||
1. Download Tailscale APK from official website
|
||||
2. Transfer to Shield via USB drive or network
|
||||
3. Install using file manager
|
||||
4. Grant necessary permissions
|
||||
|
||||
# Method 2: ADB Installation (Advanced)
|
||||
# From computer with ADB installed:
|
||||
adb connect [shield-ip-address]
|
||||
adb install tailscale.apk
|
||||
```
|
||||
|
||||
### **Tailscale Setup**
|
||||
```bash
|
||||
# Initial configuration
|
||||
1. Open Tailscale app
|
||||
2. Sign in with your Tailscale account
|
||||
3. Authorize the device in Tailscale admin console
|
||||
4. Verify connection to homelab network
|
||||
5. Test connectivity to homelab services
|
||||
|
||||
# Verify connection
|
||||
# From Termux or ADB shell:
|
||||
ping atlantis.vish.local
|
||||
ping 100.83.230.112 # Atlantis Tailscale IP
|
||||
```
|
||||
|
||||
### **Advanced Tailscale Configuration**
|
||||
```bash
|
||||
# Configure as exit node (optional)
|
||||
# Allows Shield to route all traffic through homelab
|
||||
1. Tailscale admin console > Machines
|
||||
2. Find NVIDIA Shield device
|
||||
3. Enable "Exit Node" capability
|
||||
4. On Shield: Settings > Use as Exit Node
|
||||
|
||||
# Subnet routing (if needed)
|
||||
# Allow access to local networks at travel location
|
||||
tailscale up --advertise-routes=192.168.1.0/24
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📺 Media Streaming Configuration
|
||||
|
||||
### **Plex Client Setup**
|
||||
```bash
|
||||
# Optimal Plex configuration for travel
|
||||
1. Install Plex app from Play Store
|
||||
2. Sign in with Plex account
|
||||
3. Server should auto-discover via Tailscale
|
||||
4. If not found manually add:
|
||||
- Server IP: atlantis.vish.local
|
||||
- Port: 32400
|
||||
- Or Tailscale IP: 100.83.230.112:32400
|
||||
|
||||
# Quality settings for travel:
|
||||
# Settings > Video Quality
|
||||
# - Home Streaming: Maximum (if good WiFi)
|
||||
# - Remote Streaming: 4 Mbps 720p (for limited bandwidth)
|
||||
# - Allow Direct Play: Enabled
|
||||
# - Allow Direct Stream: Enabled
|
||||
```
|
||||
|
||||
### **Alternative Media Apps**
|
||||
```bash
|
||||
# Jellyfin (if preferred over Plex)
|
||||
1. Install Jellyfin app from Play Store
|
||||
2. Add server: calypso.vish.local:2283
|
||||
3. Or Tailscale IP: 100.103.48.78:2283
|
||||
|
||||
# VLC for direct file access
|
||||
1. Network streams via SMB/CIFS
|
||||
2. Direct file playback from NAS
|
||||
3. Supports all media formats
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security & VPN Configuration
|
||||
|
||||
### **Secure Browsing Setup**
|
||||
```bash
|
||||
# Use Shield as secure gateway
|
||||
1. Configure Tailscale as exit node
|
||||
2. All traffic routes through homelab
|
||||
3. Benefits from Pi-hole ad blocking
|
||||
4. Secure DNS resolution
|
||||
|
||||
# Chrome browser configuration:
|
||||
# - Set homepage to homelab dashboard
|
||||
# - Bookmark frequently used services
|
||||
# - Enable sync for consistent experience
|
||||
```
|
||||
|
||||
### **SSH Access to Homelab**
|
||||
```bash
|
||||
# Using Termux for SSH connections
|
||||
1. Install Termux from Play Store
|
||||
2. Update packages: pkg update && pkg upgrade
|
||||
3. Install SSH client: pkg install openssh
|
||||
4. Generate SSH key: ssh-keygen -t ed25519
|
||||
5. Copy public key to homelab hosts
|
||||
|
||||
# Connect to homelab:
|
||||
ssh admin@atlantis.vish.local
|
||||
ssh user@homelab-vm.vish.local
|
||||
ssh pi@concord-nuc.vish.local
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏨 Travel Scenarios & Setup
|
||||
|
||||
### **Hotel Room Setup**
|
||||
```bash
|
||||
# Quick deployment in hotel room
|
||||
1. Connect Shield to hotel TV via HDMI
|
||||
2. Connect to hotel WiFi
|
||||
3. Launch Tailscale (auto-connects)
|
||||
4. Access homelab services immediately
|
||||
5. Stream personal media library
|
||||
|
||||
# Hotel WiFi considerations:
|
||||
# - May need to accept terms via browser
|
||||
# - Some hotels block VPN traffic
|
||||
# - Use mobile hotspot as backup
|
||||
```
|
||||
|
||||
### **Airbnb/Rental Property**
|
||||
```bash
|
||||
# Extended stay configuration
|
||||
1. Connect to property WiFi
|
||||
2. Set up Shield as primary entertainment
|
||||
3. Configure TV settings for optimal experience
|
||||
4. Share access with travel companions
|
||||
5. Use as work environment via homelab
|
||||
|
||||
# Family sharing:
|
||||
# - Create guest Plex accounts
|
||||
# - Share specific libraries
|
||||
# - Monitor usage via Tautulli
|
||||
```
|
||||
|
||||
### **Mobile Hotspot Integration**
|
||||
```bash
|
||||
# Using phone as internet source
|
||||
1. Enable mobile hotspot on phone
|
||||
2. Connect Shield to hotspot WiFi
|
||||
3. Monitor data usage carefully
|
||||
4. Adjust streaming quality accordingly
|
||||
|
||||
# Data-conscious settings:
|
||||
# - Plex: 2 Mbps 480p for mobile data
|
||||
# - Disable automatic updates
|
||||
# - Use offline content when possible
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Gaming & Entertainment Features
|
||||
|
||||
### **GeForce Now Integration**
|
||||
```bash
|
||||
# Cloud gaming via NVIDIA's service
|
||||
1. Install GeForce Now app
|
||||
2. Sign in with NVIDIA account
|
||||
3. Access Steam/Epic games library
|
||||
4. Stream games at 4K 60fps (with good connection)
|
||||
|
||||
# Optimal settings:
|
||||
# - Streaming Quality: Custom
|
||||
# - Bitrate: Adjust based on connection
|
||||
# - Frame Rate: 60fps preferred
|
||||
```
|
||||
|
||||
### **Local Game Streaming**
|
||||
```bash
|
||||
# Stream games from homelab PCs
|
||||
1. Install Steam Link app
|
||||
2. Discover gaming PCs on network
|
||||
3. Pair with gaming systems
|
||||
4. Stream games over Tailscale
|
||||
|
||||
# Requirements:
|
||||
# - Gaming PC with Steam installed
|
||||
# - Good network connection (5+ Mbps)
|
||||
# - Low latency connection
|
||||
```
|
||||
|
||||
### **Emulation & Retro Gaming**
|
||||
```bash
|
||||
# RetroArch for classic games
|
||||
1. Install RetroArch from Play Store
|
||||
2. Download cores for desired systems
|
||||
3. Load ROMs from homelab NAS
|
||||
4. Configure controllers
|
||||
|
||||
# ROM access via SMB:
|
||||
# - Connect to atlantis.vish.local/roms
|
||||
# - Browse by system/console
|
||||
# - Load directly from network storage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Advanced Configuration
|
||||
|
||||
### **Custom Launcher (Optional)**
|
||||
```bash
|
||||
# Replace default Android TV launcher
|
||||
1. Install alternative launcher (FLauncher, ATV Launcher)
|
||||
2. Set as default home app
|
||||
3. Customize with homelab shortcuts
|
||||
4. Create quick access to services
|
||||
|
||||
# Homelab shortcuts:
|
||||
# - Grafana dashboard
|
||||
# - Portainer interface
|
||||
# - Plex web interface
|
||||
# - Router admin panel
|
||||
```
|
||||
|
||||
### **Automation Integration**
|
||||
```bash
|
||||
# Home Assistant integration
|
||||
1. Install Home Assistant app
|
||||
2. Connect to concord-nuc.vish.local:8123
|
||||
3. Control smart home devices
|
||||
4. Automate Shield behavior
|
||||
|
||||
# Example automations:
|
||||
# - Turn on Shield when arriving home
|
||||
# - Adjust volume based on time of day
|
||||
# - Switch inputs automatically
|
||||
```
|
||||
|
||||
### **File Management**
|
||||
```bash
|
||||
# Solid Explorer configuration
|
||||
1. Add network locations:
|
||||
- SMB: //atlantis.vish.local/media
|
||||
- SMB: //calypso.vish.local/documents
|
||||
- FTP: homelab-vm.vish.local:21
|
||||
2. Enable cloud storage integration
|
||||
3. Set up automatic sync folders
|
||||
|
||||
# Use cases:
|
||||
# - Download files to Shield storage
|
||||
# - Upload photos/videos to homelab
|
||||
# - Access documents remotely
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring & Management
|
||||
|
||||
### **Performance Monitoring**
|
||||
```bash
|
||||
# Monitor Shield performance
|
||||
1. Settings > Device Preferences > About
|
||||
2. Check storage usage regularly
|
||||
3. Monitor network performance
|
||||
4. Clear cache when needed
|
||||
|
||||
# Network diagnostics:
|
||||
# - WiFi Analyzer app for signal strength
|
||||
# - Speedtest app for bandwidth testing
|
||||
# - Ping tools for latency checking
|
||||
```
|
||||
|
||||
### **Remote Management**
|
||||
```bash
|
||||
# ADB over network (advanced)
|
||||
1. Enable ADB over network in Developer Options
|
||||
2. Connect from computer: adb connect [shield-ip]:5555
|
||||
3. Execute commands remotely
|
||||
4. Install/manage apps REDACTED_APP_PASSWORD
|
||||
|
||||
# Useful ADB commands:
|
||||
adb shell pm list packages # List installed apps
|
||||
adb install app.apk # Install APK remotely
|
||||
adb shell input keyevent 3 # Simulate home button
|
||||
adb shell screencap /sdcard/screen.png # Screenshot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### **Common Issues & Solutions**
|
||||
```bash
|
||||
# Tailscale connection problems:
|
||||
1. Check internet connectivity
|
||||
2. Restart Tailscale app
|
||||
3. Re-authenticate if needed
|
||||
4. Verify firewall settings
|
||||
|
||||
# Plex streaming issues:
|
||||
1. Check server status in homelab
|
||||
2. Test direct IP connection
|
||||
3. Adjust quality settings
|
||||
4. Clear Plex app cache
|
||||
|
||||
# WiFi connectivity problems:
|
||||
1. Forget and reconnect to network
|
||||
2. Check for interference
|
||||
3. Use 5GHz band if available
|
||||
4. Reset network settings if needed
|
||||
```
|
||||
|
||||
### **Performance Optimization**
|
||||
```bash
|
||||
# Improve Shield performance:
|
||||
1. Clear app caches regularly
|
||||
2. Uninstall unused applications
|
||||
3. Restart device weekly
|
||||
4. Keep storage under 80% full
|
||||
|
||||
# Network optimization:
|
||||
1. Use wired connection when possible
|
||||
2. Position close to WiFi router
|
||||
3. Avoid interference sources
|
||||
4. Update router firmware
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Travel Checklist
|
||||
|
||||
### **Pre-Travel Setup**
|
||||
```bash
|
||||
☐ Update Shield to latest firmware
|
||||
☐ Update all apps
|
||||
☐ Verify Tailscale connectivity
|
||||
☐ Test Plex streaming
|
||||
☐ Download offline content if needed
|
||||
☐ Charge remote control
|
||||
☐ Pack HDMI cable (if needed)
|
||||
☐ Pack power adapter
|
||||
☐ Verify homelab services are running
|
||||
☐ Set up mobile hotspot backup
|
||||
```
|
||||
|
||||
### **At Destination**
|
||||
```bash
|
||||
☐ Connect to local WiFi
|
||||
☐ Test internet speed
|
||||
☐ Launch Tailscale
|
||||
☐ Verify homelab connectivity
|
||||
☐ Test media streaming
|
||||
☐ Configure TV settings
|
||||
☐ Set up any shared access
|
||||
☐ Monitor data usage (if on mobile)
|
||||
```
|
||||
|
||||
### **Departure Cleanup**
|
||||
```bash
|
||||
☐ Sign out of local accounts
|
||||
☐ Clear browser data
|
||||
☐ Remove WiFi networks
|
||||
☐ Reset any personalized settings
|
||||
☐ Verify no personal data left on device
|
||||
☐ Pack all accessories
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Integration with Homelab Services
|
||||
|
||||
### **Service Access URLs**
|
||||
```bash
|
||||
# Via Tailscale (always accessible):
|
||||
Plex: http://100.83.230.112:32400
|
||||
Jellyfin: http://100.103.48.78:2283
|
||||
Grafana: http://100.83.230.112:7099
|
||||
Home Assistant: http://100.67.40.126:8123
|
||||
Portainer: http://100.83.230.112:9000
|
||||
Router Admin: http://192.168.1.1
|
||||
|
||||
# Via local DNS (when on home network):
|
||||
Plex: http://atlantis.vish.local:32400
|
||||
Jellyfin: http://calypso.vish.local:2283
|
||||
Grafana: http://atlantis.vish.local:7099
|
||||
```
|
||||
|
||||
### **Backup & Sync**
|
||||
```bash
|
||||
# Automatic backup of Shield data
|
||||
1. Configure Syncthing on Shield (if available)
|
||||
2. Sync important folders to homelab
|
||||
3. Backup app configurations
|
||||
4. Store in homelab for easy restore
|
||||
|
||||
# Manual backup process:
|
||||
1. Use ADB to pull important data
|
||||
2. Store configurations in homelab Git repo
|
||||
3. Document custom settings
|
||||
4. Create restore procedures
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- [Tailscale Setup Guide](../docs/infrastructure/tailscale-setup-guide.md)
|
||||
- [Travel Networking Guide](../docs/infrastructure/comprehensive-travel-setup.md)
|
||||
- [Plex Configuration](../docs/services/individual/plex.md)
|
||||
- [Home Assistant Integration](../docs/services/individual/home-assistant.md)
|
||||
|
||||
---
|
||||
|
||||
**💡 Pro Tip**: The NVIDIA Shield TV Pro is an incredibly versatile travel companion. With proper setup, it provides seamless access to your entire homelab infrastructure from anywhere in the world, making travel feel like home.
|
||||
|
||||
**🔄 Maintenance**: Update this configuration monthly and test all functionality before important trips.
|
||||
5
hosts/edge/rpi5-kevin/PMC_readme.txt
Normal file
5
hosts/edge/rpi5-kevin/PMC_readme.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
vish@pi-5-kevin:~/paper $ cat start.sh
|
||||
#!/bin/bash
|
||||
java -Xms2G -Xmx4G -jar paper-1.21.7-26.jar nogui
|
||||
|
||||
#Run this in a screen
|
||||
67
hosts/edge/rpi5-kevin/minecraft_server.txt
Normal file
67
hosts/edge/rpi5-kevin/minecraft_server.txt
Normal file
@@ -0,0 +1,67 @@
|
||||
minecraft server.properties
|
||||
|
||||
# Minecraft server properties - Optimized for Raspberry Pi 5 (8GB RAM) Creative Server
|
||||
|
||||
# --- Gameplay Settings ---
|
||||
gamemode=creative
|
||||
difficulty=peaceful
|
||||
pvp=false
|
||||
spawn-protection=0
|
||||
allow-flight=true
|
||||
generate-structures=true
|
||||
level-name=world
|
||||
level-seed=
|
||||
level-type=minecraft\:flat
|
||||
|
||||
# --- Server Limits & Performance ---
|
||||
max-players=10
|
||||
view-distance=6
|
||||
simulation-distance=4
|
||||
max-tick-time=100000
|
||||
sync-chunk-writes=false
|
||||
entity-broadcast-range-percentage=75
|
||||
max-world-size=29999984
|
||||
|
||||
# --- Networking ---
|
||||
server-ip=
|
||||
server-port=25565
|
||||
rate-limit=10
|
||||
network-compression-threshold=512
|
||||
use-native-transport=true
|
||||
|
||||
# --- Online Access ---
|
||||
online-mode=true
|
||||
enforce-secure-profile=false
|
||||
prevent-proxy-connections=false
|
||||
white-list=true
|
||||
enforce-whitelist=true
|
||||
|
||||
# --- RCON/Query (disabled for now) ---
|
||||
enable-rcon=false
|
||||
rcon.port=25575
|
||||
rcon.password=
|
||||
"REDACTED_PASSWORD"
|
||||
query.port=25565
|
||||
|
||||
# --- Other Options ---
|
||||
motd=Welcome to Kevin's world
|
||||
op-permission-level=4
|
||||
function-permission-level=2
|
||||
player-idle-timeout=0
|
||||
text-filtering-config=
|
||||
text-filtering-version=0
|
||||
resource-pack=
|
||||
resource-pack-sha1=
|
||||
resource-pack-id=
|
||||
require-resource-pack=false
|
||||
resource-pack-prompt=
|
||||
initial-enabled-packs=vanilla
|
||||
initial-disabled-packs=
|
||||
bug-report-link=
|
||||
broadcast-console-to-ops=true
|
||||
broadcast-rcon-to-ops=true
|
||||
debug=false
|
||||
enable-command-block=false
|
||||
enable-jmx-monitoring=false
|
||||
pause-when-empty-seconds=-1
|
||||
accepts-transfers=false
|
||||
28
hosts/edge/rpi5-vish/diun.yaml
Normal file
28
hosts/edge/rpi5-vish/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:
|
||||
15
hosts/edge/rpi5-vish/dozzle-agent.yaml
Normal file
15
hosts/edge/rpi5-vish/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
|
||||
15
hosts/edge/rpi5-vish/glances.yaml
Normal file
15
hosts/edge/rpi5-vish/glances.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
# Glances - Real-time system monitoring
|
||||
# Web UI: http://<host-ip>:61208
|
||||
# Provides: CPU, memory, disk, network, Docker container stats
|
||||
|
||||
services:
|
||||
glances:
|
||||
image: nicolargo/glances:latest
|
||||
container_name: glances
|
||||
pid: host
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- GLANCES_OPT=--webserver
|
||||
restart: unless-stopped
|
||||
67
hosts/edge/rpi5-vish/immich/docker-compose.yml
Normal file
67
hosts/edge/rpi5-vish/immich/docker-compose.yml
Normal file
@@ -0,0 +1,67 @@
|
||||
# Immich - Photo/video backup solution
|
||||
# URL: https://photos.vishconcord.synology.me
|
||||
# Port: 2283
|
||||
# Google Photos alternative with ML-powered features
|
||||
version: "3.8"
|
||||
|
||||
name: immich
|
||||
|
||||
services:
|
||||
immich-server:
|
||||
container_name: immich_server
|
||||
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "2283:2283"
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:2283/api/server-info"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# You can enable this later if you really want object detection or face recognition.
|
||||
# It’ll work on the Pi 5, but very, very slowly.
|
||||
# immich-machine-learning:
|
||||
# container_name: immich_machine_learning
|
||||
# image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
|
||||
# volumes:
|
||||
# - model-cache:/cache
|
||||
# env_file:
|
||||
# - .env
|
||||
# restart: unless-stopped
|
||||
# healthcheck:
|
||||
# disable: false
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: docker.io/valkey/valkey:8-bookworm
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "REDACTED_PASSWORD"
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
POSTGRES_INITDB_ARGS: "--data-checksums"
|
||||
volumes:
|
||||
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
|
||||
shm_size: 128mb
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
model-cache:
|
||||
22
hosts/edge/rpi5-vish/samba.conf
Normal file
22
hosts/edge/rpi5-vish/samba.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
# Samba share on rpi5-vish (192.168.0.66)
|
||||
# Shares the NVMe storagepool for access by other hosts on the LAN
|
||||
#
|
||||
# Mounted by:
|
||||
# - homelab-vm: /mnt/pi5_storagepool (creds: /etc/samba/.pi5_credentials)
|
||||
# - Atlantis: /volume1/pi5_storagepool (creds: /root/.pi5_smb_creds)
|
||||
#
|
||||
# To apply: copy relevant [storagepool] block into /etc/samba/smb.conf on pi-5
|
||||
# Set SMB password: "REDACTED_PASSWORD" -e 'PASSWORD\nPASSWORD' | sudo smbpasswd -a vish -s
|
||||
#
|
||||
# pi-5 also mounts from Atlantis via NFS:
|
||||
# /mnt/atlantis_data → 192.168.0.200:/volume1/data (media/torrents/usenet)
|
||||
|
||||
[storagepool]
|
||||
path = /mnt/storagepool
|
||||
browseable = yes
|
||||
read only = no
|
||||
guest ok = no
|
||||
valid users = vish
|
||||
force user = vish
|
||||
create mask = 0664
|
||||
directory mask = 0775
|
||||
27
hosts/edge/rpi5-vish/scrutiny-collector.yaml
Normal file
27
hosts/edge/rpi5-vish/scrutiny-collector.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
# Scrutiny Collector — pi-5 (Raspberry Pi 5)
|
||||
#
|
||||
# Ships SMART data to the hub on homelab-vm.
|
||||
# pi-5 has 2 NVMe drives (M.2 HAT):
|
||||
# - nvme0n1: Micron 7450 480GB
|
||||
# - nvme1n1: Samsung 970 EVO Plus 500GB
|
||||
# NVMe not auto-discovered by smartctl --scan; uses explicit config.
|
||||
# collector.yaml lives at: /home/vish/scrutiny/collector.yaml
|
||||
#
|
||||
# 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
|
||||
- /home/vish/scrutiny/collector.yaml:/opt/scrutiny/config/collector.yaml:ro
|
||||
devices:
|
||||
- /dev/nvme0n1
|
||||
- /dev/nvme1n1
|
||||
environment:
|
||||
COLLECTOR_API_ENDPOINT: "http://100.67.40.126:8090"
|
||||
restart: unless-stopped
|
||||
13
hosts/edge/rpi5-vish/uptime-kuma.yaml
Normal file
13
hosts/edge/rpi5-vish/uptime-kuma.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Uptime Kuma - Self-hosted monitoring tool
|
||||
# Web UI: http://<host-ip>:3001
|
||||
# Features: HTTP(s), TCP, Ping, DNS monitoring with notifications
|
||||
|
||||
services:
|
||||
uptime-kuma:
|
||||
image: louislam/uptime-kuma:latest
|
||||
container_name: uptime-kuma
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /home/vish/docker/kuma/data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
restart: unless-stopped
|
||||
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
|
||||
153
hosts/physical/concord-nuc/homeassistant/dashboards/cameras.yaml
Normal file
153
hosts/physical/concord-nuc/homeassistant/dashboards/cameras.yaml
Normal file
@@ -0,0 +1,153 @@
|
||||
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: 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
|
||||
564
hosts/physical/concord-nuc/homeassistant/dashboards/homelab.yaml
Normal file
564
hosts/physical/concord-nuc/homeassistant/dashboards/homelab.yaml
Normal file
@@ -0,0 +1,564 @@
|
||||
title: Homelab
|
||||
views:
|
||||
- type: sections
|
||||
title: Homelab
|
||||
path: homelab
|
||||
icon: mdi:server
|
||||
max_columns: 3
|
||||
sections:
|
||||
|
||||
# ---- Calendar (Baikal via CalDAV) ----
|
||||
- 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
|
||||
|
||||
# ---- Media / arr suite ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:play-circle
|
||||
heading: Media Stack
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: sensor.atlantis
|
||||
name: Plex - 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 Shows
|
||||
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
|
||||
- type: tile
|
||||
entity: sensor.atlantis_library_other_videos
|
||||
name: Other
|
||||
icon: mdi:video-vintage
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.sonarr_queue_2
|
||||
name: Sonarr Queue
|
||||
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 Queue
|
||||
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: Series
|
||||
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: Sonarr Wanted
|
||||
icon: mdi:television-off
|
||||
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: tile
|
||||
entity: sensor.bazarr_badges
|
||||
name: Bazarr Missing
|
||||
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
|
||||
|
||||
# ---- Downloads ----
|
||||
- type: grid
|
||||
column_span: 1
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:download
|
||||
heading: Downloads
|
||||
heading_style: title
|
||||
|
||||
- type: tile
|
||||
entity: sensor.sabnzbd_speed
|
||||
name: SABnzbd Speed
|
||||
icon: mdi:download-network
|
||||
tap_action:
|
||||
action: url
|
||||
url_path: http://100.83.230.112:8080
|
||||
|
||||
- type: tile
|
||||
entity: sensor.sabnzbd_queue
|
||||
name: SAB Queue
|
||||
icon: mdi:tray-full
|
||||
|
||||
- type: entities
|
||||
title: SAB Details
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: sensor.sabnzbd_status
|
||||
name: Status
|
||||
icon: mdi:information-outline
|
||||
- entity: sensor.sabnzbd_left_to_download
|
||||
name: Left to Download
|
||||
icon: mdi:download
|
||||
- entity: sensor.sabnzbd_queue_count
|
||||
name: Queue Size
|
||||
icon: mdi:tray-full
|
||||
- entity: sensor.sabnzbd_free_disk_space
|
||||
name: Disk Free
|
||||
icon: mdi:harddisk-plus
|
||||
- entity: sensor.sabnzbd_daily_total
|
||||
name: Today
|
||||
icon: mdi:calendar-today
|
||||
|
||||
- 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
|
||||
|
||||
# ---- 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
|
||||
|
||||
# ---- Proxmox VE (pve host) ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
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: tile
|
||||
entity: sensor.pve_uptime
|
||||
name: Uptime
|
||||
icon: mdi:clock-outline
|
||||
|
||||
- type: entities
|
||||
title: PVE Host
|
||||
show_header_toggle: false
|
||||
entities:
|
||||
- entity: binary_sensor.pve_status
|
||||
name: Status
|
||||
- entity: sensor.pve_memory_usage
|
||||
name: Memory Used
|
||||
- entity: sensor.pve_max_memory_usage
|
||||
name: Memory Total
|
||||
- entity: sensor.pve_disk_usage
|
||||
name: Disk Used
|
||||
- entity: sensor.pve_max_disk_usage
|
||||
name: Disk Total
|
||||
- entity: binary_sensor.pve_backup_status
|
||||
name: Backup Status
|
||||
- entity: sensor.pve_last_backup
|
||||
name: Last Backup
|
||||
- entity: sensor.pve_backup_duration
|
||||
name: Backup Duration
|
||||
|
||||
- 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?"
|
||||
|
||||
# ---- Atlantis NAS (Synology DSM) ----
|
||||
- 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: Volume 1 Used (TB)
|
||||
- entity: sensor.atlantis_volume_1_status
|
||||
name: Volume 1 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 (Synology DSM) ----
|
||||
- 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: Volume 1 Used (TB)
|
||||
- entity: sensor.calypso_volume_1_status
|
||||
name: Volume 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
|
||||
|
||||
# ---- Quick launch (one-tap links) ----
|
||||
- type: grid
|
||||
column_span: 2
|
||||
cards:
|
||||
- type: heading
|
||||
icon: mdi:rocket-launch
|
||||
heading: Quick Launch
|
||||
heading_style: title
|
||||
|
||||
- type: grid
|
||||
columns: 4
|
||||
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: Homelab UI
|
||||
icon: mdi:home-analytics
|
||||
tap_action:
|
||||
action: url
|
||||
url_path: http://homelab.tail.vish.gg:3100
|
||||
@@ -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)($$|/)'
|
||||
43
hosts/proxmox/lxc/tdarr-node/docker-compose.yaml
Normal file
43
hosts/proxmox/lxc/tdarr-node/docker-compose.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
# Tdarr Node - NUC-QSV (Intel Quick Sync Video hardware transcoding)
|
||||
# Runs on Proxmox LXC 103 (tdarr-node)
|
||||
# Connects to Tdarr Server on Synology (atlantis) at 192.168.0.200
|
||||
#
|
||||
# NFS Mounts required in LXC:
|
||||
# /mnt/media -> 192.168.0.200:/volume1/data/media
|
||||
# /mnt/cache -> 192.168.0.200:/volume3/usenet
|
||||
#
|
||||
# Important: Both /temp and /cache must be mounted to the same base path
|
||||
# as the server's cache to avoid path mismatch errors during file operations.
|
||||
|
||||
services:
|
||||
tdarr-node:
|
||||
image: ghcr.io/haveagitgat/tdarr_node@sha256:dc23becc667f77d2489b1042REDACTED_GITEA_TOKEN # v2.67.01 - pinned to match server
|
||||
container_name: tdarr-node
|
||||
labels:
|
||||
- com.centurylinklabs.watchtower.enable=false
|
||||
security_opt:
|
||||
- apparmor:unconfined
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- nodeName=NUC
|
||||
- serverIP=192.168.0.200
|
||||
- serverPort=8266
|
||||
- inContainer=true
|
||||
- ffmpegVersion=6
|
||||
devices:
|
||||
- /dev/dri:/dev/dri # Intel QSV hardware acceleration
|
||||
volumes:
|
||||
- ./configs:/app/configs
|
||||
- ./logs:/app/logs
|
||||
- /mnt/media:/media
|
||||
- /mnt/cache/tdarr_cache:/temp # Server uses both /temp and /cache
|
||||
- /mnt/cache/tdarr_cache:/cache # Must mount both for node compatibility
|
||||
restart: unless-stopped
|
||||
|
||||
# Auto-update: DISABLED — tdarr nodes must stay version-synced with the server.
|
||||
# Image is pinned to a specific digest. To update all nodes at once, change the
|
||||
# digest in all 4 compose files and redeploy simultaneously.
|
||||
# Remove the hourly cron if it exists: pct exec 103 -- rm -f /etc/cron.d/tdarr-update
|
||||
0
hosts/synology/atlantis/.gitkeep
Normal file
0
hosts/synology/atlantis/.gitkeep
Normal file
19
hosts/synology/atlantis/Ubuntu_repo_sync.txt
Normal file
19
hosts/synology/atlantis/Ubuntu_repo_sync.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
# Ubuntu archive
|
||||
sudo rsync -avz --delete --ignore-errors --no-perms --no-owner --no-group \
|
||||
rsync://archive.ubuntu.com/ubuntu \
|
||||
/volume1/archive/repo/mirror/archive.ubuntu.com/ubuntu
|
||||
|
||||
# Ubuntu security
|
||||
sudo rsync -avz --delete --ignore-errors --no-perms --no-owner --no-group \
|
||||
rsync://security.ubuntu.com/ubuntu \
|
||||
/volume1/archive/repo/mirror/security.ubuntu.com/ubuntu
|
||||
|
||||
# Debian archive
|
||||
sudo rsync -avz --delete --ignore-errors --no-perms --no-owner --no-group \
|
||||
rsync://deb.debian.org/debian \
|
||||
/volume1/archive/repo/mirror/deb.debian.org/debian
|
||||
|
||||
# Debian security
|
||||
sudo rsync -avz --delete --ignore-errors --no-perms --no-owner --no-group \
|
||||
rsync://security.debian.org/debian-security \
|
||||
/volume1/archive/repo/mirror/security.debian.org/debian-security
|
||||
24
hosts/synology/atlantis/adguard.yaml
Normal file
24
hosts/synology/atlantis/adguard.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# AdGuard Home — Atlantis (backup DNS)
|
||||
# Port: 53 (DNS), 9080 (web UI)
|
||||
# Purpose: Backup split-horizon DNS resolver
|
||||
# Primary: Calypso (192.168.0.250)
|
||||
# Backup: Atlantis (192.168.0.200) ← this instance
|
||||
#
|
||||
# Same filters, rewrites, and upstream DNS as Calypso.
|
||||
# Router DHCP: primary=192.168.0.250, secondary=192.168.0.200
|
||||
|
||||
services:
|
||||
adguard:
|
||||
image: adguard/adguardhome:latest
|
||||
container_name: AdGuard
|
||||
network_mode: host
|
||||
mem_limit: 2g
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: on-failure:5
|
||||
volumes:
|
||||
- /volume1/docker/adguard/config:/opt/adguardhome/conf:rw
|
||||
- /volume1/docker/adguard/data:/opt/adguardhome/work:rw
|
||||
environment:
|
||||
TZ: America/Los_Angeles
|
||||
41
hosts/synology/atlantis/anythingllm/docker-compose.yml
Normal file
41
hosts/synology/atlantis/anythingllm/docker-compose.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
# AnythingLLM - Local RAG-powered document assistant
|
||||
# URL: http://192.168.0.200:3101
|
||||
# Port: 3101
|
||||
# LLM: Olares qwen3:32b via OpenAI-compatible API
|
||||
# Docs: docs/services/individual/anythingllm.md
|
||||
|
||||
services:
|
||||
anythingllm:
|
||||
image: mintplexlabs/anythingllm:latest
|
||||
container_name: anythingllm
|
||||
hostname: anythingllm
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ports:
|
||||
- "3101:3001"
|
||||
volumes:
|
||||
- /volume2/metadata/docker/anythingllm/storage:/app/server/storage:rw
|
||||
- /volume1/archive/paperless/backup_2026-03-15/media/documents/archive:/documents/paperless-archive:ro
|
||||
- /volume1/archive/paperless/backup_2026-03-15/media/documents/originals:/documents/paperless-originals:ro
|
||||
environment:
|
||||
STORAGE_DIR: /app/server/storage
|
||||
SERVER_PORT: 3001
|
||||
DISABLE_TELEMETRY: "true"
|
||||
TZ: America/Los_Angeles
|
||||
# LLM Provider - Olares qwen3:32b via OpenAI-compatible API
|
||||
LLM_PROVIDER: generic-openai
|
||||
GENERIC_OPEN_AI_BASE_PATH: http://192.168.0.145:31434/v1
|
||||
GENERIC_OPEN_AI_MODEL_PREF: qwen3-coder:latest
|
||||
GENERIC_OPEN_AI_MAX_TOKENS: 8192
|
||||
GENERIC_OPEN_AI_API_KEY: not-needed # pragma: allowlist secret
|
||||
GENERIC_OPEN_AI_MODEL_TOKEN_LIMIT: 65536
|
||||
# Embedding and Vector DB
|
||||
EMBEDDING_ENGINE: native
|
||||
VECTOR_DB: lancedb
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3001/api/ping"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
restart: unless-stopped
|
||||
498
hosts/synology/atlantis/arr-suite/docker-compose.yml
Normal file
498
hosts/synology/atlantis/arr-suite/docker-compose.yml
Normal file
@@ -0,0 +1,498 @@
|
||||
# Arr Suite - Media automation stack
|
||||
# Services: Sonarr, Radarr, Prowlarr, Bazarr, Lidarr, Tdarr, LazyLibrarian, Audiobookshelf
|
||||
# Manages TV shows, movies, music, books, audiobooks downloads and organization
|
||||
# GitOps Test: Stack successfully deployed and auto-updating
|
||||
#
|
||||
# Storage Configuration (2026-02-01):
|
||||
# - Downloads: /volume3/usenet (Synology SNV5420 NVMe RAID1 - 621 MB/s)
|
||||
# - Media: /volume1/data (SATA RAID6 - 84TB)
|
||||
# - Configs: /volume2/metadata/docker2 (Crucial P310 NVMe RAID1)
|
||||
#
|
||||
# Volume 3 created for fast download performance using 007revad's Synology_M2_volume script
|
||||
#
|
||||
# Theming: Self-hosted theme.park (Dracula theme)
|
||||
# - TP_DOMAIN uses docker gateway IP to reach host's theme-park container
|
||||
# - Deploy theme-park stack first: Atlantis/theme-park/theme-park.yaml
|
||||
version: "3.8"
|
||||
|
||||
x-themepark: &themepark
|
||||
TP_SCHEME: "http"
|
||||
TP_DOMAIN: "192.168.0.200:8580"
|
||||
TP_THEME: "dracula"
|
||||
|
||||
networks:
|
||||
media2_net:
|
||||
driver: bridge
|
||||
name: media2_net
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.24.0.0/24
|
||||
gateway: 172.24.0.1
|
||||
|
||||
services:
|
||||
|
||||
wizarr:
|
||||
image: ghcr.io/wizarrrr/wizarr:latest
|
||||
container_name: wizarr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- DISABLE_BUILTIN_AUTH=true
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/wizarr:/data/database
|
||||
ports:
|
||||
- "5690:5690"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.2
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
tautulli:
|
||||
image: lscr.io/linuxserver/tautulli:latest
|
||||
container_name: tautulli
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:tautulli
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/tautulli:/config
|
||||
ports:
|
||||
- "8181:8181"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.12
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
prowlarr:
|
||||
image: lscr.io/linuxserver/prowlarr:latest
|
||||
container_name: prowlarr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:prowlarr
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/prowlarr:/config
|
||||
ports:
|
||||
- "9696:9696"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.6
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
flaresolverr:
|
||||
image: flaresolverr/flaresolverr:latest
|
||||
container_name: flaresolverr
|
||||
environment:
|
||||
- TZ=America/Los_Angeles
|
||||
ports:
|
||||
- "8191:8191"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.4
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
sabnzbd:
|
||||
image: lscr.io/linuxserver/sabnzbd:latest
|
||||
container_name: sabnzbd
|
||||
network_mode: host
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:sabnzbd
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/sabnzbd:/config
|
||||
- /volume3/usenet/incomplete:/data/incomplete
|
||||
- /volume3/usenet/complete:/data/complete
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
jackett:
|
||||
image: lscr.io/linuxserver/jackett:latest
|
||||
container_name: jackett
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:jackett
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/jackett:/config
|
||||
- /volume1/data:/downloads
|
||||
ports:
|
||||
- "9117:9117"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.11
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
sonarr:
|
||||
image: lscr.io/linuxserver/sonarr:latest
|
||||
container_name: sonarr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:sonarr
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/sonarr:/config
|
||||
- /volume1/data:/data
|
||||
- /volume3/usenet:/sab
|
||||
- /volume2/torrents:/downloads # Deluge download dir — required for torrent import
|
||||
ports:
|
||||
- "8989:8989"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.7
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
lidarr:
|
||||
image: lscr.io/linuxserver/lidarr:latest
|
||||
container_name: lidarr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:lidarr
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/lidarr:/config
|
||||
- /volume1/data:/data
|
||||
- /volume3/usenet:/sab
|
||||
# arr-scripts: custom init scripts for Deezer integration via deemix
|
||||
# Config: /volume2/metadata/docker2/lidarr/extended.conf (contains ARL token, not in git)
|
||||
# Setup: https://github.com/RandomNinjaAtk/arr-scripts
|
||||
- /volume2/metadata/docker2/lidarr-scripts/custom-services.d:/custom-services.d
|
||||
- /volume2/metadata/docker2/lidarr-scripts/custom-cont-init.d:/custom-cont-init.d
|
||||
ports:
|
||||
- "8686:8686"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.9
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
radarr:
|
||||
image: lscr.io/linuxserver/radarr:latest
|
||||
container_name: radarr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:radarr
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/radarr:/config
|
||||
- /volume1/data:/data
|
||||
- /volume3/usenet:/sab
|
||||
- /volume2/torrents:/downloads # Deluge download dir — required for torrent import
|
||||
ports:
|
||||
- "7878:7878"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.8
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
# Readarr retired - replaced with LazyLibrarian + Audiobookshelf
|
||||
|
||||
lazylibrarian:
|
||||
image: lscr.io/linuxserver/lazylibrarian:latest
|
||||
container_name: lazylibrarian
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:lazylibrarian|ghcr.io/linuxserver/mods:lazylibrarian-calibre
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/lazylibrarian:/config
|
||||
- /volume1/data:/data
|
||||
- /volume3/usenet:/sab
|
||||
- /volume2/torrents:/downloads # Deluge download dir — required for torrent import
|
||||
- /volume2/metadata/docker2/lazylibrarian-scripts/custom-cont-init.d:/custom-cont-init.d # patch tracker-less torrent handling
|
||||
ports:
|
||||
- "5299:5299"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.5
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
audiobookshelf:
|
||||
image: ghcr.io/advplyr/audiobookshelf:latest
|
||||
container_name: audiobookshelf
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/audiobookshelf:/config
|
||||
- /volume1/data/media/audiobooks:/audiobooks
|
||||
- /volume1/data/media/podcasts:/podcasts
|
||||
- /volume1/data/media/ebooks:/ebooks
|
||||
ports:
|
||||
- "13378:80"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.16
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
# Bazarr - subtitle management for Sonarr and Radarr
|
||||
# Web UI: http://192.168.0.200:6767
|
||||
# Language profile: English (profile ID 1), no mustContain filter
|
||||
# Providers: REDACTED_APP_PASSWORD (vishinator), podnapisi, yifysubtitles, subf2m, subsource, subdl, animetosho
|
||||
# NOTE: OpenSubtitles.com may be IP-blocked — submit unblock request at opensubtitles.com/support
|
||||
# Notifications: Signal API via homelab-vm:8080 → REDACTED_PHONE_NUMBER
|
||||
# API keys stored in: /volume2/metadata/docker2/bazarr/config/config.yaml (not in repo)
|
||||
bazarr:
|
||||
image: lscr.io/linuxserver/bazarr:latest
|
||||
container_name: bazarr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:bazarr
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/bazarr:/config
|
||||
- /volume1/data:/data
|
||||
- /volume3/usenet:/sab
|
||||
ports:
|
||||
- "6767:6767"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.10
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
whisparr:
|
||||
image: ghcr.io/hotio/whisparr:nightly
|
||||
container_name: whisparr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- TP_HOTIO=true
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/whisparr:/config
|
||||
- /volume1/data:/data
|
||||
- /volume3/usenet/complete:/sab/complete
|
||||
- /volume3/usenet/incomplete:/sab/incomplete
|
||||
ports:
|
||||
- "6969:6969"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.3
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
plex:
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
network_mode: host
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- VERSION=docker
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:plex
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/plex:/config
|
||||
- /volume1/data/media:/data/media
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr:latest
|
||||
container_name: jellyseerr
|
||||
user: "1029:100"
|
||||
environment:
|
||||
- TZ=America/Los_Angeles
|
||||
# Note: Jellyseerr theming requires CSS injection via reverse proxy or browser extension
|
||||
# theme.park doesn't support DOCKER_MODS for non-linuxserver images
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/jellyseerr:/app/config
|
||||
ports:
|
||||
- "5055:5055"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.14
|
||||
dns:
|
||||
- 9.9.9.9
|
||||
- 1.1.1.1
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
gluetun:
|
||||
image: qmcgaw/gluetun:v3.38.0
|
||||
container_name: gluetun
|
||||
privileged: true
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
|
||||
labels:
|
||||
- com.centurylinklabs.watchtower.enable=false
|
||||
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
|
||||
# --- WireGuard ---
|
||||
- VPN_SERVICE_PROVIDER=custom
|
||||
- VPN_TYPE=wireguard
|
||||
|
||||
- WIREGUARD_PRIVATE_KEY=aAavqcZ6sx3IlgiH5Q8m/6w33mBu4M23JBM8N6cBKEU= # pragma: allowlist secret
|
||||
- WIREGUARD_ADDRESSES=10.2.0.2/32
|
||||
- WIREGUARD_DNS=10.2.0.1
|
||||
|
||||
- WIREGUARD_PUBLIC_KEY=FrVOQ+Dy0StjfwNtbJygJCkwSJt6ynlGbQwZBZWYfhc=
|
||||
- WIREGUARD_ALLOWED_IPS=0.0.0.0/0,::/0
|
||||
|
||||
- WIREGUARD_ENDPOINT_IP=79.127.185.193
|
||||
- WIREGUARD_ENDPOINT_PORT=51820
|
||||
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/gluetun:/gluetun
|
||||
|
||||
ports:
|
||||
- "8112:8112" # Deluge WebUI
|
||||
- "58946:58946" # Torrent TCP
|
||||
- "58946:58946/udp" # Torrent UDP
|
||||
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.20
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO /dev/null http://127.0.0.1:9999 2>/dev/null || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 6
|
||||
start_period: 30s
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
deluge:
|
||||
image: lscr.io/linuxserver/deluge:latest
|
||||
container_name: deluge
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- DOCKER_MODS=ghcr.io/themepark-dev/theme.park:deluge
|
||||
- TP_SCHEME=http
|
||||
- TP_DOMAIN=192.168.0.200:8580
|
||||
- TP_THEME=dracula
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/deluge:/config
|
||||
- /volume2/torrents:/downloads
|
||||
network_mode: "service:gluetun"
|
||||
depends_on:
|
||||
gluetun:
|
||||
condition: service_healthy
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
tdarr:
|
||||
image: ghcr.io/haveagitgat/tdarr@sha256:048ae8ed4de8e9f0de51ad73REDACTED_GITEA_TOKEN # v2.67.01 - pinned to keep all nodes in sync
|
||||
container_name: tdarr
|
||||
labels:
|
||||
- com.centurylinklabs.watchtower.enable=false
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
- serverIP=0.0.0.0
|
||||
- serverPort=8266
|
||||
- webUIPort=8265
|
||||
- internalNode=true
|
||||
- inContainer=true
|
||||
- ffmpegVersion=6
|
||||
- nodeName=Atlantis
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/tdarr/server:/app/server
|
||||
- /volume2/metadata/docker2/tdarr/configs:/app/configs
|
||||
- /volume2/metadata/docker2/tdarr/logs:/app/logs
|
||||
- /volume1/data/media:/media
|
||||
- /volume3/usenet/tdarr_cache:/temp
|
||||
- /volume3/usenet/tdarr_cache:/cache # Fix: internal node uses /cache path
|
||||
ports:
|
||||
- "8265:8265"
|
||||
- "8266:8266"
|
||||
networks:
|
||||
media2_net:
|
||||
ipv4_address: 172.24.0.15
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
154
hosts/synology/atlantis/arr-suite/install.sh
Executable file
154
hosts/synology/atlantis/arr-suite/install.sh
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Arr-Suite Installer — Atlantis (192.168.0.200)
|
||||
# =============================================================================
|
||||
# One-line install:
|
||||
# bash <(curl -fsSL https://git.vish.gg/Vish/homelab/raw/branch/main/hosts/synology/atlantis/arr-suite/install.sh)
|
||||
#
|
||||
# What this installs:
|
||||
# Sonarr, Radarr, Lidarr, Bazarr, Prowlarr, Jackett, FlaresolverR
|
||||
# SABnzbd, Deluge (via gluetun VPN), Tdarr, LazyLibrarian
|
||||
# Audiobookshelf, Whisparr, Plex, Jellyseerr, Tautulli, Wizarr
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Synology DSM with Container Manager (Docker)
|
||||
# - /volume1/data, /volume2/metadata/docker2, /volume3/usenet, /volume2/torrents
|
||||
# - PUID=1029, PGID=100 (DSM user: vish)
|
||||
# - WireGuard credentials for gluetun (must be set in compose or env)
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="https://git.vish.gg/Vish/homelab"
|
||||
COMPOSE_URL="${REPO_URL}/raw/branch/main/hosts/synology/atlantis/arr-suite/docker-compose.yml"
|
||||
DOCKER="${DOCKER_BIN:-/usr/local/bin/docker}"
|
||||
STACK_DIR="/volume2/metadata/docker2/arr-suite"
|
||||
COMPOSE_FILE="${STACK_DIR}/docker-compose.yml"
|
||||
|
||||
# Colours
|
||||
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
|
||||
|
||||
# ── Preflight ─────────────────────────────────────────────────────────────────
|
||||
|
||||
info "Arr-Suite installer starting"
|
||||
|
||||
[[ $(id -u) -eq 0 ]] || error "Run as root (sudo bash install.sh)"
|
||||
command -v "$DOCKER" &>/dev/null || error "Docker not found at $DOCKER — set DOCKER_BIN env var"
|
||||
|
||||
for vol in /volume1/data /volume2/metadata/docker2 /volume3/usenet /volume2/torrents; do
|
||||
[[ -d "$vol" ]] || warn "Volume $vol does not exist — create it before starting services"
|
||||
done
|
||||
|
||||
# ── Required directories ───────────────────────────────────────────────────────
|
||||
|
||||
info "Creating config directories..."
|
||||
SERVICES=(
|
||||
sonarr radarr lidarr bazarr prowlarr jackett sabnzbd
|
||||
deluge gluetun tdarr/server tdarr/configs tdarr/logs
|
||||
lazylibrarian audiobookshelf whisparr plex jellyseerr
|
||||
tautulli wizarr
|
||||
)
|
||||
for svc in "${SERVICES[@]}"; do
|
||||
mkdir -p "/volume2/metadata/docker2/${svc}"
|
||||
done
|
||||
|
||||
# Download directories
|
||||
mkdir -p \
|
||||
/volume3/usenet/complete \
|
||||
/volume3/usenet/incomplete \
|
||||
/volume3/usenet/tdarr_cache \
|
||||
/volume2/torrents/complete \
|
||||
/volume2/torrents/incomplete
|
||||
|
||||
# Media library
|
||||
mkdir -p \
|
||||
/volume1/data/media/tv \
|
||||
/volume1/data/media/movies \
|
||||
/volume1/data/media/music \
|
||||
/volume1/data/media/audiobooks \
|
||||
/volume1/data/media/podcasts \
|
||||
/volume1/data/media/ebooks \
|
||||
/volume1/data/media/misc
|
||||
|
||||
# Lidarr arr-scripts directories
|
||||
mkdir -p \
|
||||
/volume2/metadata/docker2/lidarr-scripts/custom-cont-init.d \
|
||||
/volume2/metadata/docker2/lidarr-scripts/custom-services.d
|
||||
|
||||
# ── Lidarr arr-scripts bootstrap ──────────────────────────────────────────────
|
||||
|
||||
INIT_SCRIPT="/volume2/metadata/docker2/lidarr-scripts/custom-cont-init.d/scripts_init.bash"
|
||||
if [[ ! -f "$INIT_SCRIPT" ]]; then
|
||||
info "Downloading arr-scripts init script..."
|
||||
curl -fsSL "https://raw.githubusercontent.com/RandomNinjaAtk/arr-scripts/main/lidarr/scripts_init.bash" \
|
||||
-o "$INIT_SCRIPT" || warn "Failed to download arr-scripts init — download manually from RandomNinjaAtk/arr-scripts"
|
||||
chmod +x "$INIT_SCRIPT"
|
||||
fi
|
||||
|
||||
# ── Download compose file ──────────────────────────────────────────────────────
|
||||
|
||||
info "Downloading docker-compose.yml..."
|
||||
mkdir -p "$STACK_DIR"
|
||||
curl -fsSL "$COMPOSE_URL" -o "$COMPOSE_FILE" || error "Failed to download compose file from $COMPOSE_URL"
|
||||
|
||||
# ── Warn about secrets ────────────────────────────────────────────────────────
|
||||
|
||||
warn "==================================================================="
|
||||
warn "ACTION REQUIRED before starting:"
|
||||
warn ""
|
||||
warn "1. Set gluetun WireGuard credentials in:"
|
||||
warn " $COMPOSE_FILE"
|
||||
warn " - WIREGUARD_PRIVATE_KEY"
|
||||
warn " - WIREGUARD_PUBLIC_KEY"
|
||||
warn " - WIREGUARD_ENDPOINT_IP"
|
||||
warn ""
|
||||
warn "2. Set Lidarr Deezer ARL token:"
|
||||
warn " /volume2/metadata/docker2/lidarr/extended.conf"
|
||||
warn " arlToken=\"<your-arl-token>\""
|
||||
warn " Get from: deezer.com -> DevTools -> Cookies -> arl"
|
||||
warn ""
|
||||
warn "3. Set Plex claim token (optional, for initial setup):"
|
||||
warn " https://www.plex.tv/claim"
|
||||
warn " Add to compose: PLEX_CLAIM=<token>"
|
||||
warn "==================================================================="
|
||||
|
||||
# ── Pull images ───────────────────────────────────────────────────────────────
|
||||
|
||||
read -rp "Pull all images now? (y/N): " pull_images
|
||||
if [[ "${pull_images,,}" == "y" ]]; then
|
||||
info "Pulling images (this may take a while)..."
|
||||
"$DOCKER" compose -f "$COMPOSE_FILE" pull
|
||||
fi
|
||||
|
||||
# ── Start stack ───────────────────────────────────────────────────────────────
|
||||
|
||||
read -rp "Start all services now? (y/N): " start_services
|
||||
if [[ "${start_services,,}" == "y" ]]; then
|
||||
info "Starting arr-suite..."
|
||||
"$DOCKER" compose -f "$COMPOSE_FILE" up -d
|
||||
info "Done! Services starting..."
|
||||
echo ""
|
||||
echo "Service URLs:"
|
||||
echo " Sonarr: http://192.168.0.200:8989"
|
||||
echo " Radarr: http://192.168.0.200:7878"
|
||||
echo " Lidarr: http://192.168.0.200:8686"
|
||||
echo " Prowlarr: http://192.168.0.200:9696"
|
||||
echo " SABnzbd: http://192.168.0.200:8080"
|
||||
echo " Deluge: http://192.168.0.200:8112 (password: "REDACTED_PASSWORD"
|
||||
echo " Bazarr: http://192.168.0.200:6767"
|
||||
echo " Tdarr: http://192.168.0.200:8265"
|
||||
echo " Whisparr: http://192.168.0.200:6969"
|
||||
echo " Plex: http://192.168.0.200:32400/web"
|
||||
echo " Jellyseerr: http://192.168.0.200:5055"
|
||||
echo " Audiobookshelf:http://192.168.0.200:13378"
|
||||
echo " LazyLibrarian: http://192.168.0.200:5299"
|
||||
echo " Tautulli: http://192.168.0.200:8181"
|
||||
echo " Wizarr: http://192.168.0.200:5690"
|
||||
echo " Jackett: http://192.168.0.200:9117"
|
||||
fi
|
||||
|
||||
info "Install complete."
|
||||
info "Docs: https://git.vish.gg/Vish/homelab/src/branch/main/docs/services/individual/"
|
||||
18
hosts/synology/atlantis/arr-suite/jellyseerr.yaml
Normal file
18
hosts/synology/atlantis/arr-suite/jellyseerr.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr:latest
|
||||
container_name: jellyseerr
|
||||
user: 1029:65536 #YOUR_UID_AND_GID
|
||||
environment:
|
||||
- TZ=America/Los_Angeles #CHANGE_TO_YOUR_TZ
|
||||
volumes:
|
||||
- /volume1/docker2/jellyseerr:/app/config
|
||||
ports:
|
||||
- 5055:5055/tcp
|
||||
network_mode: synobridge
|
||||
dns: #DNS Servers to help with speed issues some have
|
||||
- 9.9.9.9
|
||||
- 1.1.1.1
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
163
hosts/synology/atlantis/arr-suite/plex.yaml
Normal file
163
hosts/synology/atlantis/arr-suite/plex.yaml
Normal file
@@ -0,0 +1,163 @@
|
||||
# =============================================================================
|
||||
# PLEX MEDIA SERVER - DISASTER RECOVERY CONFIGURATION
|
||||
# =============================================================================
|
||||
#
|
||||
# SERVICE OVERVIEW:
|
||||
# - Primary media streaming server for homelab
|
||||
# - Serves 4K movies, TV shows, music, and photos
|
||||
# - Hardware transcoding enabled via Intel Quick Sync
|
||||
# - Critical service for media consumption
|
||||
#
|
||||
# DISASTER RECOVERY NOTES:
|
||||
# - Configuration stored in /volume1/docker2/plex (CRITICAL BACKUP)
|
||||
# - Media files in /volume1/data/media (128TB+ library)
|
||||
# - Database contains watch history, metadata, user preferences
|
||||
# - Hardware transcoding requires Intel GPU access (/dev/dri)
|
||||
#
|
||||
# BACKUP PRIORITY: HIGH
|
||||
# - Config backup: Daily automated backup required
|
||||
# - Media backup: Secondary NAS sync (Calypso)
|
||||
# - Database backup: Included in config volume
|
||||
#
|
||||
# RECOVERY TIME OBJECTIVE (RTO): 30 minutes
|
||||
# RECOVERY POINT OBJECTIVE (RPO): 24 hours
|
||||
#
|
||||
# DEPENDENCIES:
|
||||
# - Volume1 must be accessible (current issue: SSD cache failure)
|
||||
# - Intel GPU drivers for hardware transcoding
|
||||
# - Network connectivity for remote access
|
||||
# - Plex Pass subscription for premium features
|
||||
#
|
||||
# PORTS USED:
|
||||
# - 32400/tcp: Main Plex web interface and API
|
||||
# - 3005/tcp: Plex Home Theater via Plex Companion
|
||||
# - 8324/tcp: Plex for Roku via Plex Companion
|
||||
# - 32469/tcp: Plex DLNA Server
|
||||
# - 1900/udp: Plex DLNA Server
|
||||
# - 32410/udp, 32412/udp, 32413/udp, 32414/udp: GDM Network discovery
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
plex:
|
||||
# CONTAINER IMAGE:
|
||||
# - linuxserver/plex: Community-maintained, regularly updated
|
||||
# - Alternative: plexinc/pms-docker (official but less frequent updates)
|
||||
# - Version pinning recommended for production: linuxserver/plex:1.32.8
|
||||
image: linuxserver/plex:latest
|
||||
|
||||
# CONTAINER NAME:
|
||||
# - Fixed name for easy identification and management
|
||||
# - Used in monitoring, logs, and backup scripts
|
||||
container_name: plex
|
||||
|
||||
# NETWORK CONFIGURATION:
|
||||
# - host mode: Required for Plex auto-discovery and DLNA
|
||||
# - Allows Plex to bind to all network interfaces
|
||||
# - Enables UPnP/DLNA functionality for smart TVs
|
||||
# - SECURITY NOTE: Exposes all container ports to host
|
||||
network_mode: host
|
||||
|
||||
environment:
|
||||
# USER/GROUP PERMISSIONS:
|
||||
# - PUID=1029: User ID for file ownership (Synology 'admin' user)
|
||||
# - PGID=65536: Group ID for file access (Synology 'administrators' group)
|
||||
# - CRITICAL: Must match NAS user/group for file access
|
||||
# - Find correct values: id admin (on Synology)
|
||||
- PUID=1029 #CHANGE_TO_YOUR_UID
|
||||
- PGID=65536 #CHANGE_TO_YOUR_GID
|
||||
|
||||
# TIMEZONE CONFIGURATION:
|
||||
# - TZ: Timezone for logs, scheduling, and metadata
|
||||
# - Must match system timezone for accurate timestamps
|
||||
# - Format: Area/City (e.g., America/Los_Angeles, Europe/London)
|
||||
- TZ=America/Los_Angeles #CHANGE_TO_YOUR_TZ
|
||||
|
||||
# FILE PERMISSIONS:
|
||||
# - UMASK=022: Default file permissions (755 for dirs, 644 for files)
|
||||
# - Ensures proper read/write access for media files
|
||||
# - 022 = owner: rwx, group: r-x, other: r-x
|
||||
- UMASK=022
|
||||
|
||||
# PLEX VERSION MANAGEMENT:
|
||||
# - VERSION=docker: Use version bundled with Docker image
|
||||
# - Alternative: VERSION=latest (auto-update, not recommended for production)
|
||||
# - Alternative: VERSION=1.32.8.7639-fb6452ebf (pin specific version)
|
||||
- VERSION=docker
|
||||
|
||||
# PLEX CLAIM TOKEN:
|
||||
# - Used for initial server setup and linking to Plex account
|
||||
# - Get token from: https://plex.tv/claim (valid for 4 minutes)
|
||||
# - Leave empty after initial setup
|
||||
# - SECURITY: Remove token after claiming server
|
||||
- PLEX_CLAIM=
|
||||
|
||||
volumes:
|
||||
# CONFIGURATION VOLUME:
|
||||
# - /volume1/docker2/plex:/config
|
||||
# - Contains: Database, metadata, thumbnails, logs, preferences
|
||||
# - SIZE: ~50-100GB depending on library size
|
||||
# - BACKUP CRITICAL: Contains all user data and settings
|
||||
# - RECOVERY: Restore this volume to recover complete Plex setup
|
||||
- /volume1/docker2/plex:/config
|
||||
|
||||
# MEDIA VOLUME:
|
||||
# - /volume1/data/media:/data/media
|
||||
# - Contains: Movies, TV shows, music, photos (128TB+ library)
|
||||
# - READ-ONLY recommended for security (add :ro suffix if desired)
|
||||
# - STRUCTURE: Organized by type (movies/, tv/, music/, photos/)
|
||||
# - BACKUP: Synced to Calypso NAS for redundancy
|
||||
- /volume1/data/media:/data/media
|
||||
|
||||
devices:
|
||||
# HARDWARE TRANSCODING:
|
||||
# - /dev/dri:/dev/dri: Intel Quick Sync Video access
|
||||
# - Enables hardware-accelerated transcoding (H.264, H.265, AV1)
|
||||
# - CRITICAL: Reduces CPU usage by 80-90% during transcoding
|
||||
# - REQUIREMENT: Intel GPU with Quick Sync support
|
||||
# - TROUBLESHOOTING: Check 'ls -la /dev/dri' for render devices
|
||||
- /dev/dri:/dev/dri
|
||||
|
||||
security_opt:
|
||||
# SECURITY HARDENING:
|
||||
# - no-new-privileges: Prevents privilege escalation attacks
|
||||
# - Container cannot gain additional privileges during runtime
|
||||
# - Recommended security practice for all containers
|
||||
- no-new-privileges:true
|
||||
|
||||
# RESTART POLICY:
|
||||
# - always: Container restarts automatically on failure or system reboot
|
||||
# - CRITICAL: Ensures Plex is always available for media streaming
|
||||
# - Alternative: unless-stopped (won't restart if manually stopped)
|
||||
restart: unless-stopped
|
||||
|
||||
# =============================================================================
|
||||
# DISASTER RECOVERY PROCEDURES:
|
||||
# =============================================================================
|
||||
#
|
||||
# BACKUP VERIFICATION:
|
||||
# docker exec plex ls -la /config/Library/Application\ Support/Plex\ Media\ Server/
|
||||
#
|
||||
# MANUAL BACKUP:
|
||||
# tar -czf /volume2/backups/plex-config-$(date +%Y%m%d).tar.gz /volume1/docker2/plex/
|
||||
#
|
||||
# RESTORE PROCEDURE:
|
||||
# 1. Stop container: docker-compose down
|
||||
# 2. Restore config: tar -xzf plex-backup.tar.gz -C /volume1/docker2/
|
||||
# 3. Fix permissions: chown -R 1029:65536 /volume1/docker2/plex/
|
||||
# 4. Start container: docker-compose up -d
|
||||
# 5. Verify: Check http://atlantis.vish.local:32400/web
|
||||
#
|
||||
# TROUBLESHOOTING:
|
||||
# - No hardware transcoding: Check /dev/dri permissions and Intel GPU drivers
|
||||
# - Database corruption: Restore from backup or rebuild library
|
||||
# - Permission errors: Verify PUID/PGID match NAS user/group
|
||||
# - Network issues: Check host networking and firewall rules
|
||||
#
|
||||
# MONITORING:
|
||||
# - Health check: curl -f http://localhost:32400/identity
|
||||
# - Logs: docker logs plex
|
||||
# - Transcoding: Plex Dashboard > Settings > Transcoder
|
||||
# - Performance: Grafana dashboard for CPU/GPU usage
|
||||
#
|
||||
# =============================================================================
|
||||
29
hosts/synology/atlantis/arr-suite/prowlarr_flaresolverr.yaml
Normal file
29
hosts/synology/atlantis/arr-suite/prowlarr_flaresolverr.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
services:
|
||||
linuxserver-prowlarr:
|
||||
image: linuxserver/prowlarr:latest
|
||||
container_name: prowlarr
|
||||
environment:
|
||||
- PUID=1029 #CHANGE_TO_YOUR_UID
|
||||
- PGID=65536 #CHANGE_TO_YOUR_GID
|
||||
- TZ=America/Los_Angeles #CHANGE_TO_YOUR_TZ
|
||||
- UMASK=022
|
||||
volumes:
|
||||
- /volume1/docker2/prowlarr:/config
|
||||
ports:
|
||||
- 9696:9696/tcp
|
||||
network_mode: synobridge
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
|
||||
flaresolverr:
|
||||
image: flaresolverr/flaresolverr:latest
|
||||
container_name: flaresolverr
|
||||
environment:
|
||||
- TZ=America/Los_Angeles #CHANGE_TO_YOUR_TZ
|
||||
ports:
|
||||
- 8191:8191
|
||||
network_mode: synobridge
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
18
hosts/synology/atlantis/arr-suite/sabnzbd.yaml
Normal file
18
hosts/synology/atlantis/arr-suite/sabnzbd.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
sabnzbd:
|
||||
image: linuxserver/sabnzbd:latest
|
||||
container_name: sabnzbd
|
||||
environment:
|
||||
- PUID=1029 #CHANGE_TO_YOUR_UID
|
||||
- PGID=65536 #CHANGE_TO_YOUR_GID
|
||||
- TZ=America/Los_Angeles #CHANGE_TO_YOUR_TZ
|
||||
- UMASK=022
|
||||
volumes:
|
||||
- /volume1/docker2/sabnzbd:/config
|
||||
- /volume1/data/usenet:/data/usenet
|
||||
ports:
|
||||
- 8080:8080/tcp
|
||||
network_mode: synobridge
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
17
hosts/synology/atlantis/arr-suite/tautulli.yaml
Normal file
17
hosts/synology/atlantis/arr-suite/tautulli.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
tautulli:
|
||||
image: linuxserver/tautulli:latest
|
||||
container_name: tautulli
|
||||
environment:
|
||||
- PUID=1029 #CHANGE_TO_YOUR_UID
|
||||
- PGID=65536 #CHANGE_TO_YOUR_GID
|
||||
- TZ=America/Los_Angeles #CHANGE_TO_YOUR_TZ
|
||||
- UMASK=022
|
||||
volumes:
|
||||
- /volume1/docker2/tautulli:/config
|
||||
ports:
|
||||
- 8181:8181/tcp
|
||||
network_mode: synobridge
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
18
hosts/synology/atlantis/arr-suite/whisparr.yaml
Normal file
18
hosts/synology/atlantis/arr-suite/whisparr.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
whisparr:
|
||||
image: hotio/whisparr:nightly
|
||||
container_name: whisparr
|
||||
environment:
|
||||
- PUID=1029 #CHANGE_TO_YOUR_UID
|
||||
- PGID=65536 #CHANGE_TO_YOUR_GID
|
||||
- TZ=America/Los_Angeles #CHANGE_TO_YOUR_TZ
|
||||
- UMASK=022
|
||||
volumes:
|
||||
- /volume1/docker2/whisparr:/config
|
||||
- /volume1/data/:/data
|
||||
ports:
|
||||
- 6969:6969/tcp
|
||||
network_mode: synobridge
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
19
hosts/synology/atlantis/arr-suite/wizarr.yaml
Normal file
19
hosts/synology/atlantis/arr-suite/wizarr.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
wizarr:
|
||||
image: ghcr.io/wizarrrr/wizarr:latest
|
||||
container_name: wizarr
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=65536
|
||||
- TZ=America/Los_Angeles
|
||||
- DISABLE_BUILTIN_AUTH=false
|
||||
volumes:
|
||||
- /volume1/docker2/wizarr:/data/database
|
||||
ports:
|
||||
- 5690:5690/tcp
|
||||
network_mode: synobridge
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
18
hosts/synology/atlantis/atlantis_rsync_optimized.txt
Normal file
18
hosts/synology/atlantis/atlantis_rsync_optimized.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
ssh-keygen -t ed25519 -C "synology@atlantis"
|
||||
|
||||
|
||||
rsync -avhn --progress -e "ssh -T -c aes128-gcm@openssh.com -o Compression=no -x" \
|
||||
"/volume1/data/media/tv/Lord of Mysteries/" \
|
||||
root@100.99.156.20:/root/docker/plex/tvshows/
|
||||
|
||||
|
||||
rsync -avh --progress -e "ssh -T -c aes128-gcm@openssh.com -o Compression=no -x" \
|
||||
"/volume1/data/media/movies/Ballerina (2025)" \
|
||||
root@100.99.156.20:/root/docker/plex/movies/
|
||||
|
||||
rsync -avh --progress -e "ssh -T -c aes128-gcm@openssh.com -o Compression=no -x" \
|
||||
"/volume1/data/media/other/" \
|
||||
--include 'VID_20240328_150621.mp4' \
|
||||
--include 'VID_20240328_153720.mp4' \
|
||||
--exclude '*' \
|
||||
homelab@100.67.40.126:/home/homelab/whisper-docker/audio/
|
||||
18
hosts/synology/atlantis/baikal/baikal.yaml
Normal file
18
hosts/synology/atlantis/baikal/baikal.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Baikal - CalDAV/CardDAV server
|
||||
# Port: 8800
|
||||
# Self-hosted calendar and contacts sync server
|
||||
version: "3.7"
|
||||
|
||||
services:
|
||||
baikal:
|
||||
image: ckulka/baikal
|
||||
container_name: baikal
|
||||
ports:
|
||||
- "12852:80"
|
||||
environment:
|
||||
- PUID=1026
|
||||
- PGID=100
|
||||
volumes:
|
||||
- /volume2/metadata/docker/baikal/config:/var/www/baikal/config
|
||||
- /volume2/metadata/docker/baikal/html:/var/www/baikal/Specific
|
||||
restart: unless-stopped
|
||||
1
hosts/synology/atlantis/baikal/export_string.txt
Normal file
1
hosts/synology/atlantis/baikal/export_string.txt
Normal file
@@ -0,0 +1 @@
|
||||
https://cal.vish.gg/dav.php/calendars/vish/default?export
|
||||
20
hosts/synology/atlantis/calibre-books.yml
Normal file
20
hosts/synology/atlantis/calibre-books.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Calibre Web - E-book management
|
||||
# Port: 8083
|
||||
# Web-based e-book library with OPDS support
|
||||
name: calibre
|
||||
services:
|
||||
calibre-web:
|
||||
container_name: calibre-webui
|
||||
ports:
|
||||
- 8183:8083
|
||||
environment:
|
||||
- PUID=1026
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- DOCKER_MODS=linuxserver/mods:universal-calibre
|
||||
- OAUTHLIB_RELAX_TOKEN_SCOPE=1
|
||||
volumes:
|
||||
- /volume2/metadata/docker/calibreweb:/config
|
||||
- /volume2/metadata/docker/books:/books
|
||||
restart: unless-stopped
|
||||
image: ghcr.io/linuxserver/calibre-web
|
||||
43
hosts/synology/atlantis/cloudflare-tunnel.yaml
Normal file
43
hosts/synology/atlantis/cloudflare-tunnel.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
# Cloudflare Tunnel for Atlantis NAS
|
||||
# Provides secure external access without port forwarding
|
||||
#
|
||||
# SETUP INSTRUCTIONS:
|
||||
# 1. Go to https://one.dash.cloudflare.com/ → Zero Trust → Networks → Tunnels
|
||||
# 2. Create a new tunnel named "atlantis-tunnel"
|
||||
# 3. Copy the tunnel token (starts with eyJ...)
|
||||
# 4. Replace TUNNEL_TOKEN_HERE below with your token
|
||||
# 5. In the tunnel dashboard, add these public hostnames:
|
||||
#
|
||||
# | Public Hostname | Service |
|
||||
# |----------------------|----------------------------|
|
||||
# | pw.vish.gg | http://localhost:4080 |
|
||||
# | cal.vish.gg | http://localhost:12852 |
|
||||
# | meet.thevish.io | https://localhost:5443 |
|
||||
# | joplin.thevish.io | http://localhost:22300 |
|
||||
# | mastodon.vish.gg | http://192.168.0.154:3000 |
|
||||
# | matrix.thevish.io | http://192.168.0.154:8081 |
|
||||
# | mx.vish.gg | http://192.168.0.154:8082 |
|
||||
# | mm.crista.love | http://192.168.0.154:8065 |
|
||||
#
|
||||
# 6. Deploy this stack in Portainer
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
cloudflared:
|
||||
image: cloudflare/cloudflared:latest
|
||||
container_name: cloudflare-tunnel
|
||||
restart: unless-stopped
|
||||
command: tunnel run
|
||||
environment:
|
||||
- TUNNEL_TOKEN=${TUNNEL_TOKEN}
|
||||
network_mode: host # Needed to access localhost services and VMs
|
||||
# Alternative if you prefer bridge network:
|
||||
# networks:
|
||||
# - tunnel_net
|
||||
# extra_hosts:
|
||||
# - "host.docker.internal:host-gateway"
|
||||
|
||||
# networks:
|
||||
# tunnel_net:
|
||||
# driver: bridge
|
||||
83
hosts/synology/atlantis/derper.yaml
Normal file
83
hosts/synology/atlantis/derper.yaml
Normal file
@@ -0,0 +1,83 @@
|
||||
# Standalone DERP Relay Server — Atlantis (Home NAS)
|
||||
# =============================================================================
|
||||
# Tailscale/Headscale DERP relay for home-network fallback connectivity.
|
||||
# Serves as region 902 "Home - Atlantis" in the headscale derpmap.
|
||||
#
|
||||
# Why standalone (not behind nginx):
|
||||
# The DERP protocol does an HTTP→binary protocol switch inside TLS.
|
||||
# It is incompatible with HTTP reverse proxies. Must handle TLS directly.
|
||||
#
|
||||
# Port layout:
|
||||
# 8445/tcp — DERP relay (direct TLS, NOT proxied through NPM)
|
||||
# 3480/udp — STUN (NAT traversal hints)
|
||||
# Port 3478 taken by coturn/Jitsi, 3479 taken by coturn/Matrix on matrix-ubuntu.
|
||||
#
|
||||
# TLS cert:
|
||||
# Issued by Let's Encrypt via certbot DNS challenge (Cloudflare).
|
||||
# Cert path: /volume1/docker/derper-atl/certs/
|
||||
# Cloudflare credentials: /volume1/docker/derper-atl/secrets/cloudflare.ini
|
||||
# Auto-renewed monthly by the cert-renewer sidecar (ofelia + certbot/dns-cloudflare).
|
||||
# On first deploy or manual renewal, run:
|
||||
# docker run -it --rm \
|
||||
# -v /volume1/docker/derper-atl/certs:/etc/letsencrypt \
|
||||
# -v /volume1/docker/derper-atl/secrets:/root/.secrets:ro \
|
||||
# certbot/dns-cloudflare certonly \
|
||||
# --dns-cloudflare \
|
||||
# --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
|
||||
# -d derp-atl.vish.gg
|
||||
# Then copy certs to flat layout:
|
||||
# cp certs/live/derp-atl.vish.gg/fullchain.pem certs/live/derp-atl.vish.gg/derp-atl.vish.gg.crt
|
||||
# cp certs/live/derp-atl.vish.gg/privkey.pem certs/live/derp-atl.vish.gg/derp-atl.vish.gg.key
|
||||
#
|
||||
# Firewall / DSM rules required (one-time):
|
||||
# Allow inbound 8445/tcp and 3480/udp in DSM → Security → Firewall
|
||||
#
|
||||
# Router port forwards required (one-time, on home router):
|
||||
# 8445/tcp → 192.168.0.200 (Atlantis LAN IP, main interface)
|
||||
# 3480/udp → 192.168.0.200
|
||||
#
|
||||
# DNS: derp-atl.vish.gg → home public IP (managed by dynamicdnsupdater.yaml, unproxied)
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
derper-atl:
|
||||
image: fredliang/derper:latest
|
||||
container_name: derper-atl
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8445:8445" # DERP TLS — direct, not behind NPM
|
||||
- "3480:3480/udp" # STUN (3478 taken by coturn/Jitsi, 3479 taken by coturn/Matrix)
|
||||
volumes:
|
||||
# Full letsencrypt mount required — live/ contains symlinks into archive/
|
||||
# mounting only live/ breaks symlink resolution inside the container
|
||||
- /volume1/docker/derper-atl/certs:/etc/letsencrypt:ro
|
||||
environment:
|
||||
- DERP_DOMAIN=derp-atl.vish.gg
|
||||
- DERP_CERT_MODE=manual
|
||||
- DERP_CERT_DIR=/etc/letsencrypt/live/derp-atl.vish.gg
|
||||
- DERP_ADDR=:8445
|
||||
- DERP_STUN=true
|
||||
- DERP_STUN_PORT=3480
|
||||
- DERP_HTTP_PORT=-1 # disable plain HTTP, TLS only
|
||||
- DERP_VERIFY_CLIENTS=false # allow any node (headscale manages auth)
|
||||
|
||||
cert-renewer:
|
||||
# Runs certbot monthly via supercronic; after renewal copies certs to the
|
||||
# flat layout derper expects, then restarts derper-atl via Docker socket.
|
||||
# Schedule: 03:00 on the 1st of every month.
|
||||
image: certbot/dns-cloudflare:latest
|
||||
container_name: derper-atl-cert-renewer
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- derper-atl
|
||||
entrypoint: >-
|
||||
sh -c "
|
||||
apk add --no-cache supercronic curl &&
|
||||
echo '0 3 1 * * /renew.sh' > /crontab &&
|
||||
exec supercronic /crontab
|
||||
"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /volume1/docker/derper-atl/certs:/etc/letsencrypt
|
||||
- /volume1/docker/derper-atl/secrets:/root/.secrets:ro
|
||||
- /volume1/docker/derper-atl/renew.sh:/renew.sh:ro
|
||||
28
hosts/synology/atlantis/diun.yaml
Normal file
28
hosts/synology/atlantis/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:
|
||||
20
hosts/synology/atlantis/dockpeek.yml
Normal file
20
hosts/synology/atlantis/dockpeek.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
dockpeek:
|
||||
container_name: Dockpeek
|
||||
image: ghcr.io/dockpeek/dockpeek:latest
|
||||
healthcheck:
|
||||
test: timeout 10s bash -c ':> /dev/tcp/127.0.0.1/8000' || exit 1
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
environment:
|
||||
SECRET_KEY: "REDACTED_SECRET_KEY" # pragma: allowlist secret
|
||||
USERNAME: vish
|
||||
PASSWORD: REDACTED_PASSWORD # pragma: allowlist secret
|
||||
DOCKER_HOST: unix:///var/run/docker.sock
|
||||
ports:
|
||||
- 3812:8000
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
restart: on-failure:5
|
||||
71
hosts/synology/atlantis/documenso/documenso.yaml
Normal file
71
hosts/synology/atlantis/documenso/documenso.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:17
|
||||
container_name: Documenso-DB
|
||||
hostname: documenso-db
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-q", "-d", "documenso", "-U", "documensouser"]
|
||||
timeout: 45s
|
||||
interval: 10s
|
||||
retries: 10
|
||||
volumes:
|
||||
- /volume1/docker/documenso/db:/var/lib/postgresql/data:rw
|
||||
environment:
|
||||
POSTGRES_DB: documenso
|
||||
POSTGRES_USER: documensouser
|
||||
POSTGRES_PASSWORD: "REDACTED_PASSWORD" # pragma: allowlist secret
|
||||
restart: on-failure:5
|
||||
|
||||
documenso:
|
||||
image: documenso/documenso:latest
|
||||
container_name: Documenso
|
||||
ports:
|
||||
- 3513:3000
|
||||
volumes:
|
||||
- /volume1/docker/documenso/data:/opt/documenso:rw
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- PORT=3000
|
||||
- NEXTAUTH_SECRET="REDACTED_NEXTAUTH_SECRET" # pragma: allowlist secret
|
||||
- NEXT_PRIVATE_ENCRYPTION_KEY=y6vZRCEKo2rEsJzXlQfgXg3fLKlhiT7h # pragma: allowlist secret
|
||||
- NEXT_PRIVATE_ENCRYPTION_SECONDARY_KEY=QA7tXtw7fDExGRjrJ616hDmiJ4EReXlP # pragma: allowlist secret
|
||||
- NEXTAUTH_URL=https://documenso.thevish.io
|
||||
- NEXT_PUBLIC_WEBAPP_URL=https://documenso.thevish.io
|
||||
- NEXT_PRIVATE_INTERNAL_WEBAPP_URL=http://documenso:3000
|
||||
- NEXT_PUBLIC_MARKETING_URL=https://documenso.thevish.io
|
||||
- NEXT_PRIVATE_DATABASE_URL=postgres://documensouser:documensopass@documenso-db:5432/documenso
|
||||
- NEXT_PRIVATE_DIRECT_DATABASE_URL=postgres://documensouser:documensopass@documenso-db:5432/documenso
|
||||
- NEXT_PUBLIC_UPLOAD_TRANSPORT=database
|
||||
- NEXT_PRIVATE_SMTP_TRANSPORT=smtp-auth
|
||||
- NEXT_PRIVATE_SMTP_HOST=smtp.gmail.com
|
||||
- NEXT_PRIVATE_SMTP_PORT=587
|
||||
- NEXT_PRIVATE_SMTP_USERNAME=your-email@example.com
|
||||
- NEXT_PRIVATE_SMTP_PASSWORD="REDACTED_PASSWORD" jkbo lmag sapq # pragma: allowlist secret
|
||||
- NEXT_PRIVATE_SMTP_SECURE=false
|
||||
- NEXT_PRIVATE_SMTP_FROM_NAME=Vish
|
||||
- NEXT_PRIVATE_SMTP_FROM_ADDRESS=your-email@example.com
|
||||
- NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH=/opt/documenso/cert.p12
|
||||
#NEXT_PRIVATE_SMTP_UNSAFE_IGNORE_TLS=true
|
||||
#NEXT_PRIVATE_SMTP_APIKEY_USER=${NEXT_PRIVATE_SMTP_APIKEY_USER}
|
||||
#NEXT_PRIVATE_SMTP_APIKEY=${NEXT_PRIVATE_SMTP_APIKEY}
|
||||
#NEXT_PRIVATE_RESEND_API_KEY=${NEXT_PRIVATE_RESEND_API_KEY}
|
||||
#NEXT_PRIVATE_MAILCHANNELS_API_KEY=${NEXT_PRIVATE_MAILCHANNELS_API_KEY}
|
||||
#NEXT_PRIVATE_MAILCHANNELS_ENDPOINT=${NEXT_PRIVATE_MAILCHANNELS_ENDPOINT}
|
||||
#NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN=${NEXT_PRIVATE_MAILCHANNELS_DKIM_DOMAIN}
|
||||
#NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR=${NEXT_PRIVATE_MAILCHANNELS_DKIM_SELECTOR}
|
||||
#NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY=${NEXT_PRIVATE_MAILCHANNELS_DKIM_PRIVATE_KEY}
|
||||
#NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT=${NEXT_PUBLIC_DOCUMENT_SIZE_UPLOAD_LIMIT}
|
||||
#NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
|
||||
#NEXT_PUBLIC_DISABLE_SIGNUP=${NEXT_PUBLIC_DISABLE_SIGNUP}
|
||||
#NEXT_PRIVATE_UPLOAD_ENDPOINT=${NEXT_PRIVATE_UPLOAD_ENDPOINT}
|
||||
#NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE=${NEXT_PRIVATE_UPLOAD_FORCE_PATH_STYLE}
|
||||
#NEXT_PRIVATE_UPLOAD_REGION=${NEXT_PRIVATE_UPLOAD_REGION}
|
||||
#NEXT_PRIVATE_UPLOAD_BUCKET=${NEXT_PRIVATE_UPLOAD_BUCKET}
|
||||
#NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID=${NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID}
|
||||
#NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY=${NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY}
|
||||
#NEXT_PRIVATE_GOOGLE_CLIENT_ID=${NEXT_PRIVATE_GOOGLE_CLIENT_ID}
|
||||
#NEXT_PRIVATE_GOOGLE_CLIENT_SECRET=${NEXT_PRIVATE_GOOGLE_CLIENT_SECRET}
|
||||
19
hosts/synology/atlantis/dokuwiki.yml
Normal file
19
hosts/synology/atlantis/dokuwiki.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
# DokuWiki - Wiki platform
|
||||
# Port: 8084
|
||||
# Simple wiki without database, uses plain text files
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
dokuwiki:
|
||||
image: ghcr.io/linuxserver/dokuwiki
|
||||
container_name: dokuwiki
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8399:80"
|
||||
- "4443:443"
|
||||
environment:
|
||||
- TZ=America/Los_Angeles
|
||||
- PUID=1026
|
||||
- PGID=100
|
||||
volumes:
|
||||
- /volume2/metadata/docker/dokuwiki:/config
|
||||
21
hosts/synology/atlantis/dozzle/dozzle.yaml
Normal file
21
hosts/synology/atlantis/dozzle/dozzle.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# Dozzle - Real-time Docker log viewer
|
||||
# Port: 8892
|
||||
# Lightweight container log viewer with web UI
|
||||
# Updated: 2026-03-11
|
||||
services:
|
||||
dozzle:
|
||||
container_name: Dozzle
|
||||
image: amir20/dozzle:latest
|
||||
mem_limit: 3g
|
||||
cpu_shares: 768
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: on-failure:5
|
||||
ports:
|
||||
- 8892:8080
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /volume2/metadata/docker/dozzle:/data:rw
|
||||
environment:
|
||||
DOZZLE_AUTH_PROVIDER: simple
|
||||
DOZZLE_REMOTE_AGENT: "100.72.55.21:7007,100.77.151.40:7007,100.103.48.78:7007,100.75.252.64:7007,100.67.40.126:7007,100.82.197.124:7007,100.125.0.20:7007,100.85.21.51:7007"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user