infra: full IaC state after hf-pdocker-01 rebuild (2026-03-08)
Replaces initial Nomad/Consul/Terraform skeleton with current Docker-based infrastructure across all three nodes (hf-pdocker-01, hf-pdocker-02, bart). Key changes: - CLAUDE.md: updated service distribution reflecting hf-pdocker-01 restored - docs/plans/2026-03-08-hf-pdocker-01-rebuild.md: new rebuild plan doc - compose/bart/: ai-agents, guacamole, matrix, media, openvas, smarthome, watchtower - compose/hf-pdocker-02/: traefik, monitoring, adguard, gitea, semaphore, wikijs, unifi, vaultwarden, actual-budget, mcpo, netclaw, trivy, watchtower, n8n - compose/hf-pdocker-01/: frigate, frigate-notify, farm-agent - stacks/: all Swarm stack definitions (monitoring, traefik, smarthome, matrix, guacamole, media, ai-agents, frigate, gitea, semaphore, wikijs, unifi, etc.) - .gitignore: exclude .env credential files and acme.json certs - .env.example: placeholder credential files for all services - .claude/agents/: gitea-backup and other infrastructure agent configs - scripts/: frigate media cleanup and NAS sync scripts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Environment files containing secrets
|
||||
**/.env
|
||||
@@ -0,0 +1,30 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
actual-budget:
|
||||
image: actualbudget/actual-server:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5006:5006"
|
||||
volumes:
|
||||
- /data/configs/actual-budget:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "node -e \"fetch('http://localhost:5006/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
@@ -0,0 +1,32 @@
|
||||
services:
|
||||
adguardhome:
|
||||
image: adguard/adguardhome:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/adguardhome/work:/opt/adguardhome/work
|
||||
- /mnt/docker-data/configs/adguardhome/conf:/opt/adguardhome/conf
|
||||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
- "3000:3000/tcp"
|
||||
- "853:853/tcp"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
@@ -0,0 +1 @@
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
@@ -0,0 +1,120 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
margaret:
|
||||
image: 127.0.0.1:5050/margaret-openclaw:latest
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
user: "0:0"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HOME=/home/glyptic
|
||||
- USER=glyptic
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
volumes:
|
||||
- /mnt/infrastructure/margaret:/home/glyptic/.openclaw
|
||||
command: ["sh", "-c", "mkdir -p /home/glyptic && node --max-old-space-size=768 /usr/local/lib/node_modules/openclaw/dist/index.js gateway --allow-unconfigured"]
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
|
||||
spirit:
|
||||
image: 127.0.0.1:5050/margaret-openclaw:latest
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
user: "0:0"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HOME=/home/glyptic
|
||||
- USER=glyptic
|
||||
- OPENCLAW_STATE_DIR=/home/glyptic/.spirit
|
||||
- OPENCLAW_CONFIG_PATH=/home/glyptic/.spirit/openclaw.json
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
volumes:
|
||||
- /mnt/infrastructure/spirit:/home/glyptic/.spirit
|
||||
command: ["sh", "-c", "mkdir -p /home/glyptic && node --max-old-space-size=768 /usr/local/lib/node_modules/openclaw/dist/index.js gateway --allow-unconfigured"]
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
|
||||
bob:
|
||||
image: 127.0.0.1:5050/margaret-openclaw:latest
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
user: "0:0"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HOME=/home/glyptic
|
||||
- USER=glyptic
|
||||
- OPENCLAW_STATE_DIR=/home/glyptic/.bob
|
||||
- OPENCLAW_CONFIG_PATH=/home/glyptic/.bob/openclaw.json
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
volumes:
|
||||
- /mnt/infrastructure/bob:/home/glyptic/.bob
|
||||
command: ["sh", "-c", "mkdir -p /home/glyptic && node --max-old-space-size=768 /usr/local/lib/node_modules/openclaw/dist/index.js gateway --allow-unconfigured"]
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
|
||||
clippy:
|
||||
image: 127.0.0.1:5050/margaret-openclaw:latest
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
user: "0:0"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- HOME=/home/glyptic
|
||||
- USER=glyptic
|
||||
- OPENCLAW_STATE_DIR=/home/glyptic/.clippy
|
||||
- OPENCLAW_CONFIG_PATH=/home/glyptic/.clippy/openclaw.json
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
volumes:
|
||||
- /mnt/infrastructure/clippy:/home/glyptic/.clippy
|
||||
command: ["sh", "-c", "mkdir -p /home/glyptic && node --max-old-space-size=768 /usr/local/lib/node_modules/openclaw/dist/index.js gateway --allow-unconfigured"]
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
@@ -0,0 +1,263 @@
|
||||
mqtt:
|
||||
enabled: true
|
||||
host: mosquitto
|
||||
port: 1883
|
||||
|
||||
detectors:
|
||||
coral:
|
||||
type: cpu
|
||||
|
||||
# Intel QuickSync hardware acceleration
|
||||
ffmpeg:
|
||||
hwaccel_args: preset-intel-qsv-h264
|
||||
|
||||
# Database for event history
|
||||
database:
|
||||
path: /media/frigate/frigate.db
|
||||
|
||||
# Record settings
|
||||
record:
|
||||
enabled: true
|
||||
retain:
|
||||
days: 7
|
||||
mode: motion
|
||||
alerts:
|
||||
retain:
|
||||
days: 14
|
||||
detections:
|
||||
retain:
|
||||
days: 14
|
||||
|
||||
objects:
|
||||
track:
|
||||
- person
|
||||
- car
|
||||
- dog
|
||||
- cat
|
||||
- truck
|
||||
filters:
|
||||
person:
|
||||
min_area: 5000
|
||||
threshold: 0.75
|
||||
car:
|
||||
min_area: 10000
|
||||
threshold: 0.7
|
||||
|
||||
# Snapshots
|
||||
snapshots:
|
||||
enabled: true
|
||||
clean_copy: true
|
||||
retain:
|
||||
default: 14
|
||||
mode: motion
|
||||
|
||||
# Camera definitions
|
||||
cameras:
|
||||
# House Perimeter Cameras
|
||||
house_north:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.51:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1024
|
||||
height: 768
|
||||
fps: 5
|
||||
motion:
|
||||
mask:
|
||||
- 0,0,0,100,100,100,100,0
|
||||
zones:
|
||||
front_yard:
|
||||
coordinates: 0,768,1024,768,1024,384,0,384
|
||||
|
||||
house_west:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.52:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1024
|
||||
height: 768
|
||||
fps: 5
|
||||
|
||||
house_south:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.53:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1024
|
||||
height: 768
|
||||
fps: 5
|
||||
|
||||
house_east:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.58:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 960
|
||||
height: 540
|
||||
fps: 5
|
||||
|
||||
house_south_corner:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.61:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1280
|
||||
height: 960
|
||||
fps: 5
|
||||
objects:
|
||||
track:
|
||||
- person
|
||||
- car
|
||||
- dog
|
||||
|
||||
house_south_yard:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.62:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1280
|
||||
height: 960
|
||||
fps: 5
|
||||
|
||||
back_porch:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.63:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1280
|
||||
height: 960
|
||||
fps: 5
|
||||
|
||||
# Garage & Driveway
|
||||
garage_2:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.64:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 640
|
||||
height: 480
|
||||
fps: 5
|
||||
|
||||
# Shop Cameras
|
||||
shop_west:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.56:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1024
|
||||
height: 768
|
||||
fps: 5
|
||||
|
||||
shop_driveway:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.44.57:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1024
|
||||
height: 768
|
||||
fps: 5
|
||||
|
||||
# Other Cameras
|
||||
pool_room:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.86.142:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1280
|
||||
height: 720
|
||||
fps: 5
|
||||
|
||||
gate_camera:
|
||||
enabled: true
|
||||
ffmpeg:
|
||||
inputs:
|
||||
- path: rtsp://admin:123456@192.168.86.175:554/stream1
|
||||
roles:
|
||||
- detect
|
||||
- record
|
||||
detect:
|
||||
width: 1280
|
||||
height: 960
|
||||
fps: 5
|
||||
|
||||
# Offline cameras - commented out for now
|
||||
# front_door:
|
||||
# enabled: false
|
||||
# ffmpeg:
|
||||
# inputs:
|
||||
# - path: rtsp://admin:123456@192.168.44.59:554/stream1
|
||||
# detect:
|
||||
# width: 1024
|
||||
# height: 768
|
||||
# fps: 5
|
||||
|
||||
# driveway_up:
|
||||
# enabled: false
|
||||
# ffmpeg:
|
||||
# inputs:
|
||||
# - path: rtsp://admin:123456@192.168.86.150:554/stream1
|
||||
|
||||
# driveway_down:
|
||||
# enabled: false
|
||||
# ffmpeg:
|
||||
# inputs:
|
||||
# - path: rtsp://admin:123456@192.168.44.68:554/stream1
|
||||
|
||||
# shop_east:
|
||||
# enabled: false
|
||||
# ffmpeg:
|
||||
# inputs:
|
||||
# - path: rtsp://admin:123456@192.168.44.55:554/stream1
|
||||
|
||||
# UI settings
|
||||
ui:
|
||||
timezone: America/Chicago
|
||||
|
||||
detect:
|
||||
enabled: true
|
||||
|
||||
version: 0.16-0
|
||||
@@ -0,0 +1,41 @@
|
||||
services:
|
||||
frigate:
|
||||
image: ghcr.io/blakeblackshear/frigate:stable
|
||||
restart: unless-stopped
|
||||
shm_size: "256mb"
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
- FRIGATE_RTSP_PASSWORD=password
|
||||
volumes:
|
||||
- /data/configs/frigate/config:/config
|
||||
- /data/configs/frigate/media:/media/frigate
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
networks:
|
||||
- traefik-public
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:5000/api/version || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == bart
|
||||
labels: []
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
@@ -0,0 +1 @@
|
||||
GITEA_DB_PASSWORD=change_me
|
||||
@@ -0,0 +1,84 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3003:3000"
|
||||
- "2222:22"
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=gitea-db:5432
|
||||
- GITEA__database__NAME=gitea
|
||||
- GITEA__database__USER=gitea
|
||||
- GITEA__database__PASSWD=${GITEA_DB_PASSWORD}
|
||||
- GITEA__server__SSH_PORT=2222
|
||||
volumes:
|
||||
- /data/configs/gitea:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
depends_on:
|
||||
- gitea-db
|
||||
networks:
|
||||
- gitea-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/healthz || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
gitea-db:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=gitea
|
||||
- POSTGRES_USER=gitea
|
||||
- POSTGRES_PASSWORD=${GITEA_DB_PASSWORD}
|
||||
volumes:
|
||||
- /data/configs/gitea-db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- gitea-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U gitea -d gitea"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
|
||||
networks:
|
||||
gitea-internal:
|
||||
driver: overlay
|
||||
@@ -0,0 +1,2 @@
|
||||
GUACAMOLE_MYSQL_ROOT_PASSWORD=change_me
|
||||
GUACAMOLE_MYSQL_PASSWORD=change_me
|
||||
@@ -0,0 +1,104 @@
|
||||
services:
|
||||
guacamole:
|
||||
image: guacamole/guacamole:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- GUACD_HOSTNAME=guacd
|
||||
- MYSQL_HOSTNAME=guacamole-db
|
||||
- MYSQL_DATABASE=guacamole_db
|
||||
- MYSQL_USER=guacamole_user
|
||||
- MYSQL_PASSWORD=${GUACAMOLE_MYSQL_PASSWORD}
|
||||
ports:
|
||||
- "8199:8080"
|
||||
networks:
|
||||
- traefik-public
|
||||
- guacamole-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/guacamole/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
- "traefik.http.routers.guacamole.rule=Host(`solo.hobbs.farm`)"
|
||||
- "traefik.http.routers.guacamole.entrypoints=websecure"
|
||||
- "traefik.http.routers.guacamole.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.guacamole.loadbalancer.server.port=8080"
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
guacd:
|
||||
image: guacamole/guacd:latest
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- guacamole-internal
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
guacamole-db:
|
||||
image: mysql:8.0
|
||||
user: "1024:100"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${GUACAMOLE_MYSQL_ROOT_PASSWORD}
|
||||
- MYSQL_DATABASE=guacamole_db
|
||||
- MYSQL_USER=guacamole_user
|
||||
- MYSQL_PASSWORD=${GUACAMOLE_MYSQL_PASSWORD}
|
||||
volumes:
|
||||
- /mnt/docker-data/databases/guacamole-db:/var/lib/mysql
|
||||
networks:
|
||||
- guacamole-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 --silent"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
guacamole-internal:
|
||||
driver: overlay
|
||||
@@ -0,0 +1 @@
|
||||
MATRIX_DB_PASSWORD=change_me
|
||||
@@ -0,0 +1,127 @@
|
||||
services:
|
||||
synapse:
|
||||
image: matrixdotorg/synapse:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SYNAPSE_SERVER_NAME=mess.hobbs.farm
|
||||
- SYNAPSE_REPORT_STATS=no
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/matrix/data:/data
|
||||
ports:
|
||||
- "8008:8008"
|
||||
networks:
|
||||
- traefik-public
|
||||
- matrix-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fSs http://localhost:8008/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
- "traefik.http.services.mess.loadbalancer.server.scheme=http"
|
||||
- "traefik.http.services.mess.loadbalancer.server.port=8008"
|
||||
- "traefik.http.routers.mess.rule=Host(`mess.hobbs.farm`)"
|
||||
- "traefik.http.routers.mess.entrypoints=websecure"
|
||||
- "traefik.http.routers.mess.tls=true"
|
||||
- "traefik.http.routers.mess.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.mess.service=mess"
|
||||
- "traefik.http.routers.mess-federation.rule=Host(`mess.hobbs.farm`) && PathPrefix(`/_matrix/federation`)"
|
||||
- "traefik.http.routers.mess-federation.entrypoints=websecure"
|
||||
- "traefik.http.routers.mess-federation.tls=true"
|
||||
- "traefik.http.routers.mess-federation.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.mess-federation.service=mess"
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
|
||||
wellknown:
|
||||
image: nginx:alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/matrix/wellknown:/usr/share/nginx/html/.well-known/matrix:ro
|
||||
networks:
|
||||
- traefik-public
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:80/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.wellknown.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.wellknown.rule=Host(`hobbs.farm`) && PathPrefix(`/.well-known/matrix`)"
|
||||
- "traefik.http.routers.wellknown.entrypoints=websecure"
|
||||
- "traefik.http.routers.wellknown.tls=true"
|
||||
- "traefik.http.routers.wellknown.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.wellknown.service=wellknown"
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
|
||||
synapse-db:
|
||||
image: postgres:15-alpine
|
||||
user: "1024:100"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=synapse
|
||||
- POSTGRES_PASSWORD=${MATRIX_DB_PASSWORD}
|
||||
- POSTGRES_DB=synapse
|
||||
- POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C
|
||||
volumes:
|
||||
- /mnt/docker-data/databases/matrix-db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- matrix-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U synapse -d synapse"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
matrix-internal:
|
||||
driver: overlay
|
||||
@@ -0,0 +1,31 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
mcpo:
|
||||
image: ghcr.io/open-webui/mcpo:main
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8001:8000"
|
||||
volumes:
|
||||
- /data/configs/mcpo:/app/config
|
||||
command: ["--config", "/app/config/config.json"]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:8000/openapi.json || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
@@ -0,0 +1,195 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
volumes:
|
||||
- /data/configs/jellyfin:/config
|
||||
- /data/media:/media
|
||||
ports:
|
||||
- "8096:8096"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8096/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
|
||||
sonarr:
|
||||
image: linuxserver/sonarr:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- /data/configs/sonarr:/config
|
||||
- /data/media:/media
|
||||
ports:
|
||||
- "8989:8989"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8989/ping || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
radarr:
|
||||
image: linuxserver/radarr:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- /data/configs/radarr:/config
|
||||
- /data/media:/media
|
||||
ports:
|
||||
- "7878:7878"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:7878/ping || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
sabnzbd:
|
||||
image: linuxserver/sabnzbd:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- /data/configs/sabnzbd:/config
|
||||
- /data/media/downloads:/downloads
|
||||
ports:
|
||||
- "8082:8080"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api?mode=version || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
|
||||
nzbhydra2:
|
||||
image: linuxserver/nzbhydra2:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- /data/configs/nzbhydra:/config
|
||||
ports:
|
||||
- "5076:5076"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:5076/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
jellyseerr:
|
||||
image: fallenbagel/jellyseerr:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- /data/configs/jellyseerr:/app/config
|
||||
ports:
|
||||
- "5055:5055"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:5055/api/v1/status || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
@@ -0,0 +1,40 @@
|
||||
global:
|
||||
resolve_timeout: 5m
|
||||
|
||||
route:
|
||||
group_by: ["alertname"]
|
||||
group_wait: 30s
|
||||
group_interval: 5m
|
||||
repeat_interval: 4h
|
||||
receiver: "matrix-alerts"
|
||||
routes:
|
||||
- match:
|
||||
severity: critical
|
||||
repeat_interval: 30m
|
||||
receiver: "matrix-alerts"
|
||||
- match:
|
||||
alertname: Watchdog
|
||||
receiver: "null"
|
||||
repeat_interval: 24h
|
||||
- match:
|
||||
severity: info
|
||||
repeat_interval: 12h
|
||||
receiver: "matrix-alerts"
|
||||
|
||||
receivers:
|
||||
- name: "null"
|
||||
- name: "matrix-alerts"
|
||||
webhook_configs:
|
||||
- url: "http://matrix-alertmanager-receiver:9094/alerts/notifications"
|
||||
http_config:
|
||||
basic_auth:
|
||||
username: "alertmanager"
|
||||
password: "alertmanager-webhook"
|
||||
send_resolved: true
|
||||
|
||||
inhibit_rules:
|
||||
- source_match:
|
||||
severity: "critical"
|
||||
target_match:
|
||||
severity: "warning"
|
||||
equal: ["alertname", "instance"]
|
||||
@@ -0,0 +1,244 @@
|
||||
groups:
|
||||
- name: infrastructure_devices
|
||||
interval: 30s
|
||||
rules:
|
||||
# Unifi Device Down
|
||||
- alert: UnifiDeviceDown
|
||||
expr: unifi_device_up == 0
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
type: infrastructure
|
||||
annotations:
|
||||
summary: "Unifi device {{ $labels.name }} is offline"
|
||||
description: "Device {{ $labels.name }} ({{ $labels.model }}) at {{ $labels.ip }} has been offline for more than 2 minutes."
|
||||
|
||||
- name: server_health
|
||||
interval: 60s
|
||||
rules:
|
||||
# Server Down
|
||||
- alert: ServerDown
|
||||
expr: up{job="node"} == 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
type: server
|
||||
annotations:
|
||||
summary: "Server {{ $labels.hostname }} is DOWN"
|
||||
description: "{{ $labels.hostname }} ({{ $labels.host_ip }}) is not responding to health checks for 1 minute."
|
||||
|
||||
# High disk usage
|
||||
- alert: HighDiskUsage
|
||||
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: Disk space critically low"
|
||||
description: "Root filesystem on {{ $labels.hostname }} has less than 10% free space remaining."
|
||||
|
||||
# Moderate disk usage warning
|
||||
- alert: DiskSpaceWarning
|
||||
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 20
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: Disk space low"
|
||||
description: "Root filesystem on {{ $labels.hostname }} has less than 20% free space."
|
||||
|
||||
# High memory usage
|
||||
- alert: HighMemoryUsage
|
||||
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: High memory usage"
|
||||
description: "Memory usage on {{ $labels.hostname }} is above 90% for 5 minutes."
|
||||
|
||||
# Critical memory usage
|
||||
- alert: CriticalMemoryUsage
|
||||
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: Critical memory usage"
|
||||
description: "Memory usage on {{ $labels.hostname }} is above 95%. System may become unstable."
|
||||
|
||||
# High CPU usage (bart: 90% threshold due to Frigate/media workloads, others: 80%)
|
||||
- alert: HighCPUUsage
|
||||
expr: >
|
||||
(100 - (avg by(hostname) (rate(node_cpu_seconds_total{mode="idle",hostname!="bart"}[5m])) * 100) > 80)
|
||||
or
|
||||
(100 - (avg by(hostname) (rate(node_cpu_seconds_total{mode="idle",hostname="bart"}[5m])) * 100) > 90)
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: High CPU usage"
|
||||
description: "CPU usage on {{ $labels.hostname }} is above threshold for 10 minutes."
|
||||
|
||||
# Critical CPU usage
|
||||
- alert: CriticalCPUUsage
|
||||
expr: 100 - (avg by(hostname) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: Critical CPU usage"
|
||||
description: "CPU usage on {{ $labels.hostname }} is above 95% for 5 minutes. Performance degraded."
|
||||
|
||||
# High system load
|
||||
- alert: HighSystemLoad
|
||||
expr: node_load5 / on(hostname) count by(hostname) (node_cpu_seconds_total{mode="idle"}) > 2
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: High system load"
|
||||
description: "5-minute load average on {{ $labels.hostname }} is more than 2x CPU count."
|
||||
|
||||
# Server rebooted
|
||||
- alert: ServerRebooted
|
||||
expr: node_time_seconds - node_boot_time_seconds < 600
|
||||
for: 1m
|
||||
labels:
|
||||
severity: info
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }} rebooted"
|
||||
description: "{{ $labels.hostname }} was rebooted less than 10 minutes ago."
|
||||
|
||||
# High swap usage
|
||||
- alert: HighSwapUsage
|
||||
expr: (node_memory_SwapTotal_bytes > 0) and ((node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes) / node_memory_SwapTotal_bytes * 100 > 50)
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: High swap usage"
|
||||
description: "Swap usage on {{ $labels.hostname }} is above 50% for 5 minutes. Check container memory limits."
|
||||
|
||||
# Critical swap usage
|
||||
- alert: CriticalSwapUsage
|
||||
expr: (node_memory_SwapTotal_bytes > 0) and ((node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes) / node_memory_SwapTotal_bytes * 100 > 80)
|
||||
for: 2m
|
||||
labels:
|
||||
severity: critical
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: Critical swap usage"
|
||||
description: "Swap usage on {{ $labels.hostname }} is above 80%. System may become unresponsive."
|
||||
|
||||
# NFS mount failure (all data is on NAS - this is critical)
|
||||
- alert: NFSMountDown
|
||||
expr: node_filesystem_avail_bytes{fstype="nfs"} == 0 or absent(node_filesystem_avail_bytes{fstype="nfs"})
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
type: server
|
||||
annotations:
|
||||
summary: "{{ $labels.hostname }}: NFS mount unavailable"
|
||||
description: "NFS mount on {{ $labels.hostname }} at {{ $labels.mountpoint }} is down. All services depend on NAS storage."
|
||||
|
||||
# NAS disk space low
|
||||
- alert: NASDiskSpaceLow
|
||||
expr: (node_filesystem_avail_bytes{fstype="nfs",mountpoint="/mnt/docker-data"} / node_filesystem_size_bytes{fstype="nfs",mountpoint="/mnt/docker-data"}) * 100 < 15
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
type: server
|
||||
annotations:
|
||||
summary: "NAS disk space low"
|
||||
description: "NFS share /mnt/docker-data has less than 15% free space."
|
||||
|
||||
- name: docker_containers
|
||||
interval: 60s
|
||||
rules:
|
||||
# Service/container unreachable via blackbox HTTP probe
|
||||
# Matches all http-pdocker01, http-pdocker02, http-bart, and http-external jobs
|
||||
- alert: ContainerDown
|
||||
expr: probe_success{job=~"http-.*"} == 0
|
||||
for: 3m
|
||||
labels:
|
||||
severity: warning
|
||||
type: container
|
||||
annotations:
|
||||
summary: "Service unreachable: {{ $labels.instance }}"
|
||||
description: "Service at {{ $labels.instance }} is not responding to HTTP health checks for 3 minutes."
|
||||
|
||||
- name: service_health
|
||||
interval: 60s
|
||||
rules:
|
||||
# Service responding but very slow
|
||||
- alert: SlowHTTPResponse
|
||||
expr: probe_duration_seconds{job=~"http-.*"} > 10
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
type: container
|
||||
annotations:
|
||||
summary: "Slow response: {{ $labels.instance }}"
|
||||
description: "{{ $labels.instance }} is responding but taking more than 10 seconds."
|
||||
|
||||
# Prometheus scrape target down
|
||||
- alert: PrometheusTargetDown
|
||||
expr: up == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
type: monitoring
|
||||
annotations:
|
||||
summary: "Prometheus target down: {{ $labels.job }}"
|
||||
description: "Prometheus cannot scrape {{ $labels.instance }} (job: {{ $labels.job }}) for 5 minutes."
|
||||
|
||||
# Alerting pipeline dead-man's switch (Watchdog always fires - if it stops, pipeline is broken)
|
||||
- alert: Watchdog
|
||||
expr: vector(1)
|
||||
labels:
|
||||
severity: none
|
||||
type: monitoring
|
||||
annotations:
|
||||
summary: "Alerting pipeline active"
|
||||
description: "This is a dead-man's-switch alert. If you stop receiving this, the alerting pipeline is broken."
|
||||
|
||||
- name: frigate_alerts
|
||||
interval: 60s
|
||||
rules:
|
||||
# Frigate high memory (standalone container on hf-pdocker-01 with host network)
|
||||
- alert: FrigateHighMemory
|
||||
expr: container_memory_usage_bytes{name="frigate"} / container_spec_memory_limit_bytes{name="frigate"} > 0.85
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
type: frigate
|
||||
annotations:
|
||||
summary: "Frigate high memory usage"
|
||||
description: "Frigate is using more than 85% of allocated memory."
|
||||
|
||||
- name: service_resources
|
||||
interval: 60s
|
||||
rules:
|
||||
# Any service container using more than 90% of its memory limit
|
||||
- alert: ServiceHighMemory
|
||||
expr: >
|
||||
(container_memory_usage_bytes{name!="",name!~".*_db.*",name!~".*db$"} /
|
||||
container_spec_memory_limit_bytes{name!="",name!~".*_db.*",name!~".*db$"} > 0)
|
||||
> 0.90
|
||||
for: 5m
|
||||
labels:
|
||||
severity: warning
|
||||
type: container
|
||||
annotations:
|
||||
summary: "{{ $labels.name }}: High memory usage"
|
||||
description: "Container {{ $labels.name }} on {{ $labels.instance }} is using more than 90% of its memory limit."
|
||||
@@ -0,0 +1,17 @@
|
||||
modules:
|
||||
http_2xx:
|
||||
prober: http
|
||||
timeout: 10s
|
||||
http:
|
||||
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
|
||||
follow_redirects: true
|
||||
preferred_ip_protocol: "ip4"
|
||||
http_2xx_insecure:
|
||||
prober: http
|
||||
timeout: 10s
|
||||
http:
|
||||
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
|
||||
follow_redirects: true
|
||||
preferred_ip_protocol: "ip4"
|
||||
tls_config:
|
||||
insecure_skip_verify: true
|
||||
@@ -0,0 +1,197 @@
|
||||
services:
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
restart: unless-stopped
|
||||
user: "104"
|
||||
ports:
|
||||
- "3001:3000"
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/grafana:/var/lib/grafana
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9095:9090"
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
|
||||
- '--web.console.templates=/usr/share/prometheus/consoles'
|
||||
- '--web.enable-lifecycle'
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/prometheus:/etc/prometheus
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:9090/-/healthy || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
alertmanager:
|
||||
image: prom/alertmanager:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9093:9093"
|
||||
command:
|
||||
- '--config.file=/etc/alertmanager/alertmanager.yml'
|
||||
- '--storage.path=/alertmanager'
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/alertmanager:/etc/alertmanager
|
||||
- alertmanager-data:/alertmanager
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:9093/-/healthy || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
|
||||
node-exporter:
|
||||
image: prom/node-exporter:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9100:9100"
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.rootfs=/rootfs'
|
||||
- '--path.sysfs=/host/sys'
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: global
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
|
||||
influxdb:
|
||||
image: influxdb:1.8-alpine
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/influxdb:/var/lib/influxdb
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:8086/ping || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
|
||||
matrix-alertmanager-receiver:
|
||||
image: metio/matrix-alertmanager-receiver:2026.2.25
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9094:9094"
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/alertmanager/matrix-receiver-config.yml:/config/config.yml:ro
|
||||
command: ["--config-path", "/config/config.yml"]
|
||||
# NOTE: No healthcheck - this image is distroless (no shell), so CMD-SHELL healthchecks fail.
|
||||
# The service is verified healthy by Alertmanager successfully POSTing to it.
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
|
||||
blackbox-exporter:
|
||||
image: prom/blackbox-exporter:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9115:9115"
|
||||
command:
|
||||
- '--config.file=/config/blackbox.yml'
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/blackbox:/config:ro
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
|
||||
volumes:
|
||||
alertmanager-data:
|
||||
@@ -0,0 +1,55 @@
|
||||
http:
|
||||
address: 0.0.0.0
|
||||
port: 9094
|
||||
alerts-path-prefix: /alerts
|
||||
metrics-path: /metrics
|
||||
metrics-enabled: true
|
||||
# basic-password is required - the app has a hardcoded default basic-username of "alertmanager"
|
||||
# and will exit silently if basic-password is not set
|
||||
basic-password: "alertmanager-webhook"
|
||||
|
||||
matrix:
|
||||
# Matrix Synapse is on bart (failover from hf-pdocker-01)
|
||||
homeserver-url: http://192.168.86.167:8008
|
||||
user-id: "@alertbot:hobbs.farm"
|
||||
# Access token stored directly - ${MATRIX_ACCESS_TOKEN} env var syntax is NOT supported
|
||||
# (the app uses yaml parsing, not shell interpolation)
|
||||
access-token: "syt_YWxlcnRib3Q_eOVAYjYKWBZxymfdxhPE_0iYlOZ"
|
||||
room-mapping:
|
||||
notifications: "!jYmqxbzEsvRgKYvwMP:hobbs.farm"
|
||||
|
||||
templating:
|
||||
external-url-mapping:
|
||||
# Alertmanager and Prometheus are on hf-pdocker-02 (failover from hf-pdocker-01)
|
||||
"http://alertmanager:9093": "http://192.168.86.100:9093"
|
||||
generator-url-mapping:
|
||||
"http://prometheus:9090": "http://192.168.86.100:9095"
|
||||
computed-values:
|
||||
- values:
|
||||
color: red
|
||||
when-matching-labels:
|
||||
severity: critical
|
||||
- values:
|
||||
color: orange
|
||||
when-matching-labels:
|
||||
severity: warning
|
||||
- values:
|
||||
color: blue
|
||||
when-matching-labels:
|
||||
severity: info
|
||||
# NOTE: Templates use .Alert (singular) not .Alerts (plural)
|
||||
# This application calls the template once per individual alert, not per group
|
||||
firing-template: |
|
||||
<h3>ALERT: {{ .Alert.Labels.alertname }}</h3>
|
||||
<table>
|
||||
<tr><td><b>Instance:</b></td><td>{{ .Alert.Labels.instance }}</td></tr>
|
||||
<tr><td><b>Severity:</b></td><td>{{ .Alert.Labels.severity }}</td></tr>
|
||||
<tr><td><b>Summary:</b></td><td>{{ .Alert.Annotations.summary }}</td></tr>
|
||||
<tr><td><b>Description:</b></td><td>{{ .Alert.Annotations.description }}</td></tr>
|
||||
</table>
|
||||
resolved-template: |
|
||||
<h3>RESOLVED: {{ .Alert.Labels.alertname }}</h3>
|
||||
<table>
|
||||
<tr><td><b>Instance:</b></td><td>{{ .Alert.Labels.instance }}</td></tr>
|
||||
<tr><td><b>Summary:</b></td><td>{{ .Alert.Annotations.summary }}</td></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,163 @@
|
||||
global:
|
||||
scrape_interval: 1m
|
||||
evaluation_interval: 1m
|
||||
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
- 'alertmanager:9093'
|
||||
|
||||
rule_files:
|
||||
- "alerts.yml"
|
||||
|
||||
scrape_configs:
|
||||
# Prometheus self-monitoring
|
||||
- job_name: 'prometheus'
|
||||
scrape_interval: 1m
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
# Node Exporter (system metrics) - all 3 swarm nodes
|
||||
- job_name: 'node'
|
||||
static_configs:
|
||||
- targets: ['192.168.86.192:9100']
|
||||
labels:
|
||||
hostname: 'hf-pdocker-01'
|
||||
host_ip: '192.168.86.192'
|
||||
- targets: ['192.168.86.167:9100']
|
||||
labels:
|
||||
hostname: 'bart'
|
||||
host_ip: '192.168.86.167'
|
||||
- targets: ['192.168.86.100:9100']
|
||||
labels:
|
||||
hostname: 'hf-pdocker-02'
|
||||
host_ip: '192.168.86.100'
|
||||
|
||||
# Blackbox Exporter self-monitoring
|
||||
- job_name: 'blackbox'
|
||||
static_configs:
|
||||
- targets: ['blackbox-exporter:9115']
|
||||
|
||||
# Matrix Alertmanager Receiver metrics (running on hf-pdocker-02 during failover)
|
||||
- job_name: 'matrix-alertmanager-receiver'
|
||||
scrape_interval: 1m
|
||||
static_configs:
|
||||
- targets: ['192.168.86.100:9094']
|
||||
labels:
|
||||
hostname: 'hf-pdocker-02'
|
||||
|
||||
# HTTP Health Checks - hf-pdocker-01 services (native services only; failover services tracked in other jobs)
|
||||
- job_name: 'http-pdocker01'
|
||||
metrics_path: /probe
|
||||
params:
|
||||
module: [http_2xx]
|
||||
static_configs:
|
||||
- targets:
|
||||
- http://192.168.86.192:5000 # Frigate NVR (standalone, host network)
|
||||
- http://192.168.86.192:5199/health # Frigate Notify
|
||||
- http://192.168.86.192:8888/health # Farm Manager API
|
||||
- http://192.168.86.192:8889/health # Farm Agent
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: blackbox-exporter:9115
|
||||
|
||||
# HTTP Health Checks - hf-pdocker-02 services (native + failover from hf-pdocker-01)
|
||||
- job_name: 'http-pdocker02'
|
||||
metrics_path: /probe
|
||||
params:
|
||||
module: [http_2xx]
|
||||
static_configs:
|
||||
- targets:
|
||||
- http://192.168.86.100:3003 # Gitea
|
||||
- http://192.168.86.100:3004 # Semaphore
|
||||
- http://192.168.86.100:3002 # Wiki.js
|
||||
- http://192.168.86.100:26226 # Vaultwarden
|
||||
- http://192.168.86.100:5006 # Actual Budget
|
||||
- http://192.168.86.100:8001/openapi.json # MCPO
|
||||
- http://192.168.86.100:8889/health # Farm Agent
|
||||
- http://192.168.86.100:3001 # Grafana (FAILOVER from hf-pdocker-01)
|
||||
- http://192.168.86.100:9095 # Prometheus (FAILOVER from hf-pdocker-01)
|
||||
- http://192.168.86.100:9093 # Alertmanager (FAILOVER from hf-pdocker-01)
|
||||
- http://192.168.86.100:3000 # AdGuard Home (FAILOVER from hf-pdocker-01)
|
||||
- http://192.168.86.100:9000 # Portainer
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: blackbox-exporter:9115
|
||||
|
||||
# HTTPS Health Checks - services with self-signed certs
|
||||
- job_name: 'http-pdocker02-https'
|
||||
metrics_path: /probe
|
||||
params:
|
||||
module: [http_2xx_insecure]
|
||||
static_configs:
|
||||
- targets:
|
||||
- https://192.168.86.100:8443 # Unifi Controller (self-signed cert)
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: blackbox-exporter:9115
|
||||
|
||||
# HTTP Health Checks - bart services (native + failover from hf-pdocker-01)
|
||||
- job_name: 'http-bart'
|
||||
metrics_path: /probe
|
||||
params:
|
||||
module: [http_2xx]
|
||||
static_configs:
|
||||
- targets:
|
||||
- http://192.168.86.167:8096 # Jellyfin
|
||||
- http://192.168.86.167:8989 # Sonarr
|
||||
- http://192.168.86.167:7878 # Radarr
|
||||
- http://192.168.86.167:8082 # SABnzbd
|
||||
- http://192.168.86.167:5076 # NZBHydra2
|
||||
- http://192.168.86.167:5055 # Jellyseerr
|
||||
- http://192.168.86.167:9392 # OpenVAS
|
||||
- http://192.168.86.167:8889/health # Farm Agent
|
||||
- http://192.168.86.167:8123 # Home Assistant (FAILOVER from hf-pdocker-01)
|
||||
- http://192.168.86.167:6052 # ESPHome (FAILOVER from hf-pdocker-01)
|
||||
- http://192.168.86.167:8199/guacamole/ # Guacamole (FAILOVER from hf-pdocker-01)
|
||||
- http://192.168.86.167:8008 # Matrix Synapse (FAILOVER from hf-pdocker-01)
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: blackbox-exporter:9115
|
||||
|
||||
# HTTPS Health Checks - External domains via Traefik
|
||||
- job_name: 'http-external'
|
||||
metrics_path: /probe
|
||||
params:
|
||||
module: [http_2xx]
|
||||
static_configs:
|
||||
- targets:
|
||||
- https://mess.hobbs.farm # Matrix (via Traefik)
|
||||
- https://solo.hobbs.farm/guacamole/ # Guacamole (via Traefik)
|
||||
- https://yoda.hobbs.farm # Home Assistant
|
||||
- https://vault.hobbs.farm # Vaultwarden
|
||||
- https://git.hobbs.farm # Gitea
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: blackbox-exporter:9115
|
||||
|
||||
# Unifi Network Devices - uncomment when unifi-exporter is deployed
|
||||
# - job_name: 'unifi-devices'
|
||||
# scrape_interval: 30s
|
||||
# static_configs:
|
||||
# - targets: ['unifi-exporter:9101']
|
||||
@@ -0,0 +1,2 @@
|
||||
N8N_DB_PASSWORD=change_me
|
||||
N8N_ENCRYPTION_KEY=change_me_32_char_random_key_here
|
||||
@@ -0,0 +1,82 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
n8n:
|
||||
image: n8nio/n8n:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5678:5678"
|
||||
environment:
|
||||
- DB_TYPE=postgresdb
|
||||
- DB_POSTGRESDB_HOST=n8n-db
|
||||
- DB_POSTGRESDB_PORT=5432
|
||||
- DB_POSTGRESDB_DATABASE=n8n
|
||||
- DB_POSTGRESDB_USER=n8n
|
||||
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
|
||||
- N8N_PORT=5678
|
||||
- GENERIC_TIMEZONE=America/Chicago
|
||||
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
|
||||
volumes:
|
||||
- /data/configs/n8n:/home/node/.n8n
|
||||
depends_on:
|
||||
- n8n-db
|
||||
networks:
|
||||
- n8n-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:5678/healthz || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 120s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
n8n-db:
|
||||
image: postgres:15-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=n8n
|
||||
- POSTGRES_USER=n8n
|
||||
- POSTGRES_PASSWORD=${N8N_DB_PASSWORD}
|
||||
volumes:
|
||||
- /data/configs/n8n-db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- n8n-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
|
||||
networks:
|
||||
n8n-internal:
|
||||
driver: overlay
|
||||
@@ -0,0 +1,24 @@
|
||||
services:
|
||||
node-exporter:
|
||||
image: prom/node-exporter:latest
|
||||
hostname: "{{.Node.Hostname}}"
|
||||
ports:
|
||||
- "9100:9100"
|
||||
volumes:
|
||||
- /proc:/host/proc:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.rootfs=/rootfs'
|
||||
- '--path.sysfs=/host/sys'
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: global
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
@@ -0,0 +1,24 @@
|
||||
services:
|
||||
openvas:
|
||||
image: immauss/openvas:latest
|
||||
environment:
|
||||
- PASSWORD=admin
|
||||
- USERNAME=admin
|
||||
volumes:
|
||||
- openvas_data:/data
|
||||
ports:
|
||||
- "9392:9392"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 6G
|
||||
reservations:
|
||||
memory: 4G
|
||||
|
||||
volumes:
|
||||
openvas_data:
|
||||
@@ -0,0 +1,4 @@
|
||||
SEMAPHORE_DB_PASSWORD=change_me
|
||||
SEMAPHORE_ADMIN_PASSWORD=change_me
|
||||
SEMAPHORE_ENCRYPTION_KEY=change_me_base64_encoded_32_byte_key
|
||||
SEMAPHORE_MYSQL_ROOT_PASSWORD=change_me
|
||||
@@ -0,0 +1,89 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
semaphore:
|
||||
image: semaphoreui/semaphore:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3004:3000"
|
||||
environment:
|
||||
- SEMAPHORE_DB_DIALECT=mysql
|
||||
- SEMAPHORE_DB_HOST=semaphore-db
|
||||
- SEMAPHORE_DB_PORT=3306
|
||||
- SEMAPHORE_DB_USER=semaphore
|
||||
- SEMAPHORE_DB_PASS=${SEMAPHORE_DB_PASSWORD}
|
||||
- SEMAPHORE_DB_NAME=semaphore
|
||||
- SEMAPHORE_DB=semaphore
|
||||
- SEMAPHORE_PLAYBOOK_PATH=/tmp/semaphore
|
||||
- SEMAPHORE_ADMIN_PASSWORD=${SEMAPHORE_ADMIN_PASSWORD}
|
||||
- SEMAPHORE_ADMIN_NAME=admin
|
||||
- SEMAPHORE_ADMIN_EMAIL=admin@hobbs.farm
|
||||
- SEMAPHORE_ADMIN=admin
|
||||
- SEMAPHORE_ACCESS_KEY_ENCRYPTION=${SEMAPHORE_ENCRYPTION_KEY}
|
||||
- SEMAPHORE_LDAP_ACTIVATED=no
|
||||
volumes:
|
||||
- /data/configs/semaphore:/etc/semaphore
|
||||
- /data/configs/semaphore/playbooks:/tmp/semaphore
|
||||
depends_on:
|
||||
- semaphore-db
|
||||
networks:
|
||||
- semaphore-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/ping || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
semaphore-db:
|
||||
image: mysql:8.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${SEMAPHORE_MYSQL_ROOT_PASSWORD}
|
||||
- MYSQL_DATABASE=semaphore
|
||||
- MYSQL_USER=semaphore
|
||||
- MYSQL_PASSWORD=${SEMAPHORE_DB_PASSWORD}
|
||||
volumes:
|
||||
- /data/configs/semaphore-db:/var/lib/mysql
|
||||
networks:
|
||||
- semaphore-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 --silent"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
networks:
|
||||
semaphore-internal:
|
||||
driver: overlay
|
||||
@@ -0,0 +1,97 @@
|
||||
services:
|
||||
homeassistant:
|
||||
image: ghcr.io/home-assistant/home-assistant:stable
|
||||
restart: unless-stopped
|
||||
privileged: true
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/homeassistant:/config
|
||||
ports:
|
||||
- "8123:8123"
|
||||
dns:
|
||||
- 192.168.86.1
|
||||
- 8.8.8.8
|
||||
networks:
|
||||
- traefik-public
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
- "traefik.http.services.yoda.loadbalancer.server.scheme=http"
|
||||
- "traefik.http.services.yoda.loadbalancer.server.port=8123"
|
||||
- "traefik.http.routers.yoda.rule=Host(`yoda.hobbs.farm`)"
|
||||
- "traefik.http.routers.yoda.entrypoints=websecure"
|
||||
- "traefik.http.routers.yoda.tls=true"
|
||||
- "traefik.http.routers.yoda.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.yoda.service=yoda"
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
|
||||
esphome:
|
||||
image: esphome/esphome:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/esphome/config:/config
|
||||
ports:
|
||||
- "6052:6052"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:6052/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
|
||||
mosquitto:
|
||||
image: eclipse-mosquitto:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/mosquitto:/mosquitto
|
||||
ports:
|
||||
- "1883:1883"
|
||||
- "8883:8883"
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 128M
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
@@ -0,0 +1,25 @@
|
||||
services:
|
||||
telegraf:
|
||||
image: telegraf:latest
|
||||
restart: unless-stopped
|
||||
hostname: "{{.Node.Hostname}}"
|
||||
environment:
|
||||
- HOST_PROC=/host/proc
|
||||
- HOST_SYS=/host/sys
|
||||
- HOST_ETC=/host/etc
|
||||
volumes:
|
||||
- /mnt/docker-data/configs/telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /proc:/host/proc:ro
|
||||
- /etc:/host/etc:ro
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: global
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
@@ -0,0 +1,25 @@
|
||||
[agent]
|
||||
interval = "10s"
|
||||
round_interval = true
|
||||
metric_batch_size = 1000
|
||||
metric_buffer_limit = 10000
|
||||
|
||||
[[outputs.influxdb]]
|
||||
# InfluxDB is on hf-pdocker-02 (Compose service, not on Swarm overlay)
|
||||
urls = ["http://192.168.86.100:8086"]
|
||||
database = "telegraf"
|
||||
|
||||
# Docker input disabled due to permission issues across nodes with different GIDs
|
||||
# [[inputs.docker]]
|
||||
# endpoint = "unix:///var/run/docker.sock"
|
||||
|
||||
[[inputs.cpu]]
|
||||
percpu = true
|
||||
totalcpu = true
|
||||
|
||||
[[inputs.disk]]
|
||||
ignore_fs = ["tmpfs", "devtmpfs", "overlay"]
|
||||
|
||||
[[inputs.mem]]
|
||||
[[inputs.system]]
|
||||
[[inputs.net]]
|
||||
@@ -0,0 +1 @@
|
||||
CF_DNS_API_TOKEN=change_me_cloudflare_api_token
|
||||
@@ -0,0 +1,58 @@
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.6
|
||||
command:
|
||||
- "--api.insecure=true"
|
||||
- "--providers.swarm=true"
|
||||
- "--providers.swarm.exposedbydefault=false"
|
||||
- "--providers.file.directory=/config/dynamic"
|
||||
- "--providers.file.watch=true"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
|
||||
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
|
||||
- "--certificatesresolvers.letsencrypt.acme.email=jeremydanielhobbs@gmail.com"
|
||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
environment:
|
||||
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
|
||||
ports:
|
||||
- target: 80
|
||||
published: 80
|
||||
mode: host
|
||||
- target: 443
|
||||
published: 443
|
||||
mode: host
|
||||
- target: 8080
|
||||
published: 8090
|
||||
mode: host
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /mnt/docker-data/traefik/letsencrypt:/letsencrypt
|
||||
- /mnt/docker-data/traefik/dynamic:/config/dynamic:ro
|
||||
networks:
|
||||
- traefik-public
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://localhost:8080/api/overview || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
networks:
|
||||
traefik-public:
|
||||
driver: overlay
|
||||
attachable: true
|
||||
external: true
|
||||
@@ -0,0 +1,26 @@
|
||||
http:
|
||||
routers:
|
||||
gitea:
|
||||
rule: "Host(`git.hobbs.farm`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: gitea
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
gitea-http:
|
||||
rule: "Host(`git.hobbs.farm`)"
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
- gitea-https-redirect
|
||||
service: gitea
|
||||
middlewares:
|
||||
gitea-https-redirect:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
services:
|
||||
gitea:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.86.100:3003"
|
||||
@@ -0,0 +1,26 @@
|
||||
http:
|
||||
routers:
|
||||
vault:
|
||||
rule: "Host(`vault.hobbs.farm`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: vault
|
||||
tls:
|
||||
certResolver: letsencrypt
|
||||
vault-http:
|
||||
rule: "Host(`vault.hobbs.farm`)"
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
- vault-https-redirect
|
||||
service: vault
|
||||
middlewares:
|
||||
vault-https-redirect:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
services:
|
||||
vault:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://192.168.86.100:26226"
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
trivy:
|
||||
image: aquasec/trivy:latest
|
||||
command: server --listen 0.0.0.0:4954
|
||||
ports:
|
||||
- "4954:4954"
|
||||
volumes:
|
||||
- trivy-cache:/root/.cache/
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
deploy:
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == manager
|
||||
|
||||
volumes:
|
||||
trivy-cache:
|
||||
@@ -0,0 +1,2 @@
|
||||
UNIFI_MONGO_USER=unifi
|
||||
UNIFI_MONGO_PASS=change_me
|
||||
@@ -0,0 +1,77 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
unifi:
|
||||
image: lscr.io/linuxserver/unifi-network-application:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=America/Chicago
|
||||
- MONGO_USER=${UNIFI_MONGO_USER}
|
||||
- MONGO_PASS=${UNIFI_MONGO_PASS}
|
||||
- MONGO_HOST=unifi-db
|
||||
- MONGO_PORT=27017
|
||||
- MONGO_DBNAME=unifi
|
||||
volumes:
|
||||
- /data/configs/unifi/data:/config
|
||||
ports:
|
||||
- "8443:8443"
|
||||
- "3478:3478/udp"
|
||||
- "10001:10001/udp"
|
||||
- "8080:8080"
|
||||
- "1900:1900/udp"
|
||||
- "8843:8843"
|
||||
- "8880:8880"
|
||||
- "6789:6789"
|
||||
- "5514:5514/udp"
|
||||
networks:
|
||||
- unifi-internal
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
|
||||
unifi-db:
|
||||
image: mongo:7.0
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /data/configs/unifi/mongo-data:/data/db
|
||||
networks:
|
||||
- unifi-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mongosh --eval 'db.adminCommand(\"ping\")' --quiet"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
|
||||
networks:
|
||||
unifi-internal:
|
||||
driver: overlay
|
||||
@@ -0,0 +1,34 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
vaultwarden:
|
||||
image: vaultwarden/server:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DOMAIN=https://vault.hobbs.farm
|
||||
- SIGNUPS_ALLOWED=false
|
||||
volumes:
|
||||
- /data/configs/vaultwarden:/data
|
||||
ports:
|
||||
- "26226:80"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:80/alive || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
- WATCHTOWER_INCLUDE_RESTARTING=true
|
||||
- WATCHTOWER_POLL_INTERVAL=86400
|
||||
- WATCHTOWER_MONITOR_ONLY=false
|
||||
- TZ=America/Chicago
|
||||
- DOCKER_API_VERSION=1.44
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
@@ -0,0 +1 @@
|
||||
WIKIJS_DB_PASSWORD=change_me
|
||||
@@ -0,0 +1,77 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
wikijs:
|
||||
image: ghcr.io/requarks/wiki:2
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3002:3000"
|
||||
environment:
|
||||
- DB_TYPE=postgres
|
||||
- DB_HOST=wikijs-db
|
||||
- DB_PORT=5432
|
||||
- DB_USER=wikijs
|
||||
- DB_PASS=${WIKIJS_DB_PASSWORD}
|
||||
- DB_NAME=wiki
|
||||
depends_on:
|
||||
- wikijs-db
|
||||
networks:
|
||||
- wikijs-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3000/ || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 120s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
wikijs-db:
|
||||
image: postgres:15-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=wiki
|
||||
- POSTGRES_USER=wikijs
|
||||
- POSTGRES_PASSWORD=${WIKIJS_DB_PASSWORD}
|
||||
volumes:
|
||||
- /data/configs/wikijs/db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- wikijs-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U wikijs -d wiki"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "3"
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 1
|
||||
placement:
|
||||
constraints:
|
||||
- node.hostname == hf-pdocker-01
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=false"
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
|
||||
networks:
|
||||
wikijs-internal:
|
||||
driver: overlay
|
||||
Reference in New Issue
Block a user