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:
2026-03-08 18:51:25 -05:00
parent b1d111a011
commit de1d9e1d93
102 changed files with 12634 additions and 3 deletions
@@ -0,0 +1,22 @@
services:
farm-agent:
image: farm-agent:latest
container_name: farm-agent
restart: unless-stopped
ports:
- "8889:8889"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
mem_limit: 128M
memswap_limit: 128M
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8889/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
@@ -1,3 +0,0 @@
ANTHROPIC_API_KEY=<your-anthropic-api-key>
MATRIX_ACCESS_TOKEN=<matrix-bot-access-token>
MATRIX_ROOM_ID=<matrix-room-id>
@@ -0,0 +1,14 @@
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
COPY app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ .
EXPOSE 5199
CMD ["python", "-u", "main.py"]
@@ -0,0 +1,259 @@
import asyncio
import json
import logging
import os
import signal
import sys
import time
from collections import defaultdict
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
import aiohttp
import anthropic
import paho.mqtt.client as mqtt
from vision import analyze_snapshot
from matrix_client import MatrixClient
# --- Configuration ---
MQTT_HOST = os.environ.get("MQTT_HOST", "192.168.86.192")
MQTT_PORT = int(os.environ.get("MQTT_PORT", "1883"))
FRIGATE_URL = os.environ.get("FRIGATE_URL", "http://192.168.86.192:5000")
MATRIX_HOMESERVER = os.environ.get("MATRIX_HOMESERVER", "http://192.168.86.192:8008")
MATRIX_ACCESS_TOKEN = os.environ["MATRIX_ACCESS_TOKEN"]
MATRIX_ROOM_ID = os.environ["MATRIX_ROOM_ID"]
HEALTH_PORT = int(os.environ.get("HEALTH_PORT", "5199"))
ALLOWED_LABELS = {"person", "car", "dog", "cat", "bird"}
MIN_SCORE = 0.7
RATE_LIMIT_SECONDS = 60
DEDUP_WINDOW_SECONDS = 30
# --- Logging ---
logging.basicConfig(
level=logging.INFO,
format='{"time":"%(asctime)s","level":"%(levelname)s","module":"%(module)s","message":"%(message)s"}',
stream=sys.stdout,
)
logger = logging.getLogger(__name__)
# --- State ---
recent_events: dict[str, float] = {}
last_notify_per_camera: defaultdict[str, float] = defaultdict(float)
events_processed = 0
last_event_time: str | None = None
mqtt_connected = False
# --- Health endpoint ---
class HealthHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def do_GET(self):
if self.path == "/health":
body = json.dumps(
{
"status": "ok",
"mqtt_connected": mqtt_connected,
"events_processed": events_processed,
"last_event": last_event_time,
}
)
encoded = body.encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(encoded)))
self.end_headers()
self.wfile.write(encoded)
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass # suppress access logs
def start_health_server():
server = HTTPServer(("0.0.0.0", HEALTH_PORT), HealthHandler)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
logger.info("Health server started on port %d", HEALTH_PORT)
# --- Snapshot fetching ---
async def fetch_snapshot(event_id: str) -> bytes | None:
url = f"{FRIGATE_URL}/api/events/{event_id}/snapshot.jpg"
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status == 200:
data = await resp.read()
logger.info(
"Fetched snapshot for %s: %d bytes", event_id, len(data)
)
return data
logger.warning(
"Snapshot fetch failed for %s: HTTP %d", event_id, resp.status
)
except Exception:
logger.exception("Snapshot fetch error for %s", event_id)
return None
# --- Event processing ---
async def process_event(
event_data: dict, claude: anthropic.Anthropic, matrix: MatrixClient
):
global events_processed, last_event_time
event_type = event_data.get("type")
if event_type != "end":
return
after = event_data.get("after", {})
event_id = after.get("id", "")
camera = after.get("camera", "unknown")
label = after.get("label", "")
score = after.get("top_score", after.get("score", 0))
has_snapshot = after.get("has_snapshot", False)
# --- Filtering ---
if label not in ALLOWED_LABELS:
return
if score < MIN_SCORE:
return
if not has_snapshot:
return
# Dedup
now = time.time()
if event_id in recent_events and now - recent_events[event_id] < DEDUP_WINDOW_SECONDS:
return
recent_events[event_id] = now
# Rate limit per camera
if now - last_notify_per_camera[camera] < RATE_LIMIT_SECONDS:
logger.info("Rate limited: %s on %s", label, camera)
return
last_notify_per_camera[camera] = now
# Cleanup old dedup entries
cutoff = now - DEDUP_WINDOW_SECONDS * 2
for eid in [k for k, v in recent_events.items() if v < cutoff]:
del recent_events[eid]
logger.info(
"Processing event: %s on %s (%s, score=%.2f)", event_id, camera, label, score
)
# --- Fetch snapshot ---
snapshot = await fetch_snapshot(event_id)
# --- AI analysis ---
description = None
if snapshot:
description = analyze_snapshot(claude, snapshot)
if not description:
description = f"{label.capitalize()} detected on {camera}."
# --- Format message ---
score_pct = int(score * 100)
ts = time.strftime("%I:%M:%S %p", time.localtime())
date_str = time.strftime("%b %d, %Y", time.localtime())
frigate_link = f"{FRIGATE_URL}/review?id={event_id}"
plain = (
f"{camera} - {label.capitalize()} - {score_pct}%\n"
f"{description}\n\n"
f"View in Frigate: {frigate_link}\n"
f"{ts} - {date_str}"
)
html = (
f"<b>{camera}</b> &middot; {label.capitalize()} &middot; {score_pct}%<br/>"
f"{description}<br/><br/>"
f'<a href="{frigate_link}">View in Frigate</a><br/>'
f"<small>{ts} &middot; {date_str}</small>"
)
# --- Send to Matrix ---
if snapshot:
filename = f"{camera}_{event_id}.jpg"
mxc_uri = await matrix.upload_image(snapshot, filename)
if mxc_uri:
await matrix.send_image(mxc_uri, filename, len(snapshot))
await matrix.send_text(plain, html)
events_processed += 1
last_event_time = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
logger.info("Notification sent for %s on %s", label, camera)
# --- MQTT ---
def create_mqtt_client(
loop: asyncio.AbstractEventLoop,
claude: anthropic.Anthropic,
matrix: MatrixClient,
):
global mqtt_connected
def on_connect(client, userdata, flags, reason_code, properties):
global mqtt_connected
mqtt_connected = True
client.subscribe("frigate/events")
logger.info("MQTT connected, subscribed to frigate/events")
def on_disconnect(client, userdata, flags, reason_code, properties):
global mqtt_connected
mqtt_connected = False
logger.warning("MQTT disconnected: %s", reason_code)
def on_message(client, userdata, msg):
try:
payload = json.loads(msg.payload)
asyncio.run_coroutine_threadsafe(
process_event(payload, claude, matrix), loop
)
except json.JSONDecodeError:
logger.warning("Invalid JSON on %s", msg.topic)
except Exception:
logger.exception("Error processing MQTT message")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
return client
# --- Main ---
def main():
logger.info("Starting frigate-notify")
claude = anthropic.Anthropic()
matrix = MatrixClient(MATRIX_HOMESERVER, MATRIX_ACCESS_TOKEN, MATRIX_ROOM_ID)
loop = asyncio.new_event_loop()
loop_thread = Thread(target=loop.run_forever, daemon=True)
loop_thread.start()
start_health_server()
mqtt_client = create_mqtt_client(loop, claude, matrix)
mqtt_client.connect(MQTT_HOST, MQTT_PORT)
def shutdown(signum, frame):
logger.info("Shutting down...")
mqtt_client.disconnect()
loop.call_soon_threadsafe(loop.stop)
sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)
signal.signal(signal.SIGINT, shutdown)
mqtt_client.loop_forever()
if __name__ == "__main__":
main()
@@ -0,0 +1,84 @@
import asyncio
import logging
import time
import aiohttp
logger = logging.getLogger(__name__)
RETRY_DELAYS = [1, 2, 4]
class MatrixClient:
def __init__(self, homeserver: str, token: str, room_id: str):
self.homeserver = homeserver.rstrip("/")
self.token = token
self.room_id = room_id
self.headers = {"Authorization": f"Bearer {token}"}
async def _request(self, method: str, url: str, **kwargs) -> dict | None:
for attempt, delay in enumerate(RETRY_DELAYS):
try:
async with aiohttp.ClientSession() as session:
async with session.request(
method, url, headers=self.headers, **kwargs
) as resp:
if resp.status < 300:
return await resp.json()
body = await resp.text()
logger.warning(
"Matrix API %s (attempt %d): %s",
resp.status,
attempt + 1,
body,
)
except Exception:
logger.exception("Matrix request failed (attempt %d)", attempt + 1)
if attempt < len(RETRY_DELAYS) - 1:
await asyncio.sleep(delay)
logger.error("All Matrix retries exhausted for %s", url)
return None
async def upload_image(self, image_bytes: bytes, filename: str) -> str | None:
url = f"{self.homeserver}/_matrix/media/v3/upload?filename={filename}"
headers = {**self.headers, "Content-Type": "image/jpeg"}
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, data=image_bytes) as resp:
if resp.status < 300:
data = await resp.json()
mxc_uri = data.get("content_uri")
logger.info("Uploaded image: %s", mxc_uri)
return mxc_uri
body = await resp.text()
logger.error("Image upload failed %s: %s", resp.status, body)
except Exception:
logger.exception("Image upload failed")
return None
async def send_image(self, mxc_uri: str, filename: str, size: int) -> dict | None:
txn_id = str(int(time.time() * 1000))
url = (
f"{self.homeserver}/_matrix/client/r0/rooms/"
f"{self.room_id}/send/m.room.message/{txn_id}"
)
body = {
"msgtype": "m.image",
"body": filename,
"url": mxc_uri,
"info": {"mimetype": "image/jpeg", "size": size},
}
return await self._request("PUT", url, json=body)
async def send_text(self, plain: str, html: str) -> dict | None:
txn_id = str(int(time.time() * 1000) + 1)
url = (
f"{self.homeserver}/_matrix/client/r0/rooms/"
f"{self.room_id}/send/m.room.message/{txn_id}"
)
body = {
"msgtype": "m.text",
"body": plain,
"format": "org.matrix.custom.html",
"formatted_body": html,
}
return await self._request("PUT", url, json=body)
@@ -0,0 +1,3 @@
paho-mqtt==2.1.0
anthropic>=0.40.0
aiohttp>=3.9.0
@@ -0,0 +1,42 @@
import base64
import logging
import anthropic
logger = logging.getLogger(__name__)
PROMPT = (
"Briefly describe what's happening in this security camera image in 1-2 sentences. "
"Focus on the people, vehicles, or animals and their activity. Be concise."
)
def analyze_snapshot(client: anthropic.Anthropic, image_bytes: bytes) -> str | None:
"""Send a snapshot to Claude Haiku for description. Returns None on failure."""
try:
b64 = base64.standard_b64encode(image_bytes).decode("utf-8")
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=150,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": b64,
},
},
{"type": "text", "text": PROMPT},
],
}
],
)
description = response.content[0].text.strip()
logger.info("Vision analysis complete: %d chars", len(description))
return description
except Exception:
logger.exception("Claude Vision API failed")
return None
+488
View File
@@ -0,0 +1,488 @@
# =============================================================================
# Frigate NVR Configuration - Optimized (v4)
# =============================================================================
#
# 17 cameras total:
#
# Reolink (h264Preview paths, H.264):
# back_porch 192.168.44.63 kerberos:kerberos
# house_south_corner 192.168.44.61 admin:(blank)
# house_south_yard 192.168.44.62 admin:(blank)
# gate_camera 192.168.86.175 admin:(blank)
#
# Profile cameras (/profile1 + /profile2, H.264):
# house_north 192.168.44.51 admin:123456
# house_south 192.168.44.53 admin:123456
# house_west 192.168.44.52 admin:123456
# shop_west 192.168.44.56 admin:123456
# shop_driveway 192.168.44.57 admin:123456
# shop_east 192.168.44.55 admin:123456
#
# Stream camera (/stream1 + /stream2, HEVC):
# house_east 192.168.44.58 admin:admin
#
# Dahua-style (/cam/realmonitor, codec TBD):
# garage_2 192.168.44.64 admin:7rip8a115
#
# Amcrest w/ authbasic=64 (/cam/realmonitor):
# driveway_1 192.168.86.150 admin:7rip8a115
# driveway_2 192.168.44.68 admin:7rip8a115
# front_door_view 192.168.44.59 admin:7rip8a115
#
# Amcrest ASH43-W (HEVC, /cam/realmonitor + authbasic=64):
# pool_room 192.168.86.142 admin:bigdog01
# front_house 192.168.44.41 admin:bigdog01
#
# =============================================================================
version: 0.17-0
auth:
enabled: true
mqtt:
enabled: true
host: 192.168.86.192
port: 1883
ui:
timezone: America/Chicago
database:
path: /config/frigate.db
# =============================================================================
# go2rtc - Single connection per camera, restreamed locally
# =============================================================================
go2rtc:
webrtc:
candidates:
- 192.168.86.192:8555
- stun:8555
streams:
# --- Reolink cameras (h264Preview paths) ---------------------------------
back_porch:
- rtsp://kerberos:kerberos@192.168.44.63:554/h264Preview_01_main
back_porch_sub:
- rtsp://kerberos:kerberos@192.168.44.63:554/h264Preview_01_sub
house_south_corner:
- rtsp://admin:@192.168.44.61:554/h264Preview_01_main
house_south_corner_sub:
- rtsp://admin:@192.168.44.61:554/h264Preview_01_sub
house_south_yard:
- rtsp://admin:@192.168.44.62:554/h264Preview_01_main
house_south_yard_sub:
- rtsp://admin:@192.168.44.62:554/h264Preview_01_sub
gate_camera:
- rtsp://admin:@192.168.86.175:554/h264Preview_01_main
gate_camera_sub:
- rtsp://admin:@192.168.86.175:554/h264Preview_01_sub
# --- Profile cameras (/profile1 + /profile2) ----------------------------
house_north:
- rtsp://admin:123456@192.168.44.51:554/profile1
house_north_sub:
- rtsp://admin:123456@192.168.44.51:554/profile2
house_south:
- rtsp://admin:123456@192.168.44.53:554/profile1
house_south_sub:
- rtsp://admin:123456@192.168.44.53:554/profile2
house_west:
- rtsp://admin:123456@192.168.44.52:554/
house_west_sub:
- rtsp://admin:123456@192.168.44.52:554/profile2
shop_west:
- rtsp://admin:123456@192.168.44.56:554/profile1
shop_west_sub:
- rtsp://admin:123456@192.168.44.56:554/profile2
shop_driveway:
- rtsp://admin:123456@192.168.44.57:554/profile1
shop_driveway_sub:
- rtsp://admin:123456@192.168.44.57:554/profile2
shop_east:
- rtsp://admin:123456@192.168.44.55:554/profile1
shop_east_sub:
- rtsp://admin:123456@192.168.44.55:554/profile2
# --- Stream camera (HEVC) ------------------------------------------------
house_east:
- rtsp://admin:admin@192.168.44.58:554/stream1#video=h264
house_east_sub:
- rtsp://admin:admin@192.168.44.58:554/stream2
# --- garage_2 - Dahua-style, NOT Amcrest ---------------------------------
garage_2:
- rtsp://admin:7rip8a115@192.168.44.64:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif
garage_2_sub:
- rtsp://admin:7rip8a115@192.168.44.64:554/cam/realmonitor?channel=1&subtype=1&unicast=true&proto=Onvif
# --- Amcrest cameras w/ authbasic=64 -------------------------------------
driveway_1:
- rtsp://admin:7rip8a115@192.168.86.150:554/cam/realmonitor?channel=1&subtype=0&authbasic=64
driveway_1_sub:
- rtsp://admin:7rip8a115@192.168.86.150:554/cam/realmonitor?channel=1&subtype=1&authbasic=64
driveway_2:
- rtsp://admin:7rip8a115@192.168.44.68:554/cam/realmonitor?channel=1&subtype=0&authbasic=64
driveway_2_sub:
- rtsp://admin:7rip8a115@192.168.44.68:554/cam/realmonitor?channel=1&subtype=1&authbasic=64
front_door_view:
- rtsp://admin:7rip8a115@192.168.44.59:554/cam/realmonitor?channel=1&subtype=0&authbasic=64
front_door_view_sub:
- rtsp://admin:7rip8a115@192.168.44.59:554/cam/realmonitor?channel=1&subtype=1&authbasic=64
# --- ASH43-W cameras (HEVC, authbasic=64) --------------------------------
pool_room:
- rtsp://admin:bigdog01@192.168.86.142:554/cam/realmonitor?channel=1&subtype=0&authbasic=64#video=h264
pool_room_sub:
- rtsp://admin:bigdog01@192.168.86.142:554/cam/realmonitor?channel=1&subtype=1&authbasic=64
front_house:
- rtsp://admin:bigdog01@192.168.44.41:554/cam/realmonitor?channel=1&subtype=0&authbasic=64#video=h264
front_house_sub:
- rtsp://admin:bigdog01@192.168.44.41:554/cam/realmonitor?channel=1&subtype=1&authbasic=64
# =============================================================================
# Global ffmpeg - VAAPI hardware acceleration
# =============================================================================
ffmpeg:
hwaccel_args: preset-vaapi
# =============================================================================
model:
path: /openvino-model/ssdlite_mobilenet_v2.xml
width: 300
height: 300
input_tensor: nhwc
input_pixel_format: bgr
labelmap_path: /openvino-model/coco_91cl_bkgr.txt
detectors:
ov:
type: openvino
device: GPU
detect:
enabled: true
fps: 3
width: 640
height: 480
# =============================================================================
# Recording
# =============================================================================
record:
enabled: true
continuous:
days: 2
alerts:
retain:
days: 7
detections:
retain:
days: 7
# =============================================================================
# Snapshots
# =============================================================================
snapshots:
enabled: true
clean_copy: true
retain:
default: 7
mode: motion
# =============================================================================
# Object tracking
# =============================================================================
objects:
track:
- person
- car
filters:
person:
min_area: 5000
threshold: 0.7
car:
min_area: 10000
threshold: 0.7
# =============================================================================
# Birdseye - overview of all cameras
# =============================================================================
birdseye:
enabled: true
mode: objects
# =============================================================================
# Camera definitions
# =============================================================================
cameras:
# ---------------------------------------------------------------------------
# Reolink cameras (H.264, VAAPI OK)
# ---------------------------------------------------------------------------
back_porch:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/back_porch_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/back_porch
input_args: preset-rtsp-restream
roles:
- record
house_south_corner:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/house_south_corner_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/house_south_corner
input_args: preset-rtsp-restream
roles:
- record
house_south_yard:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/house_south_yard_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/house_south_yard
input_args: preset-rtsp-restream
roles:
- record
gate_camera:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/gate_camera_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/gate_camera
input_args: preset-rtsp-restream
roles:
- record
# ---------------------------------------------------------------------------
# Profile cameras (H.264, VAAPI OK)
# ---------------------------------------------------------------------------
house_north:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/house_north_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/house_north
input_args: preset-rtsp-restream
roles:
- record
house_south:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/house_south_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/house_south
input_args: preset-rtsp-restream
roles:
- record
house_west:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/house_west_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/house_west
input_args: preset-rtsp-restream
roles:
- record
shop_west:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/shop_west_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/shop_west
input_args: preset-rtsp-restream
roles:
- record
shop_driveway:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/shop_driveway_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/shop_driveway
input_args: preset-rtsp-restream
roles:
- record
shop_east:
enabled: false
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/shop_east_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/shop_east
input_args: preset-rtsp-restream
roles:
- record
# ---------------------------------------------------------------------------
# house_east - HEVC, VAAPI disabled (pw: admin)
# ---------------------------------------------------------------------------
house_east:
enabled: true
ffmpeg:
hwaccel_args: []
inputs:
- path: rtsp://127.0.0.1:8554/house_east_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/house_east
input_args: preset-rtsp-restream
roles:
- record
# ---------------------------------------------------------------------------
# garage_2 - Dahua-style, codec TBD, VAAPI disabled as precaution
# If H.264 confirmed, remove hwaccel_args override
# ---------------------------------------------------------------------------
garage_2:
enabled: true
ffmpeg:
hwaccel_args: []
inputs:
- path: rtsp://127.0.0.1:8554/garage_2_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/garage_2
input_args: preset-rtsp-restream
roles:
- record
# ---------------------------------------------------------------------------
# Amcrest cameras w/ authbasic=64
# Codec unknown - VAAPI disabled as precaution until confirmed H.264
# ---------------------------------------------------------------------------
driveway_1:
enabled: false
ffmpeg:
hwaccel_args: []
inputs:
- path: rtsp://127.0.0.1:8554/driveway_1_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/driveway_1
input_args: preset-rtsp-restream
roles:
- record
driveway_2:
enabled: false
ffmpeg:
hwaccel_args: []
inputs:
- path: rtsp://127.0.0.1:8554/driveway_2_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/driveway_2
input_args: preset-rtsp-restream
roles:
- record
front_door_view:
enabled: false
ffmpeg:
hwaccel_args: []
inputs:
- path: rtsp://127.0.0.1:8554/front_door_view_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/front_door_view
input_args: preset-rtsp-restream
roles:
- record
# ---------------------------------------------------------------------------
# ASH43-W cameras (HEVC main, H.264 sub - go2rtc transcodes main to H.264)
# ---------------------------------------------------------------------------
pool_room:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/pool_room_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/pool_room
input_args: preset-rtsp-restream
roles:
- record
front_house:
enabled: true
ffmpeg:
inputs:
- path: rtsp://127.0.0.1:8554/front_house_sub
input_args: preset-rtsp-restream
roles:
- detect
- path: rtsp://127.0.0.1:8554/front_house
input_args: preset-rtsp-restream
roles:
- record
@@ -0,0 +1,30 @@
services:
frigate:
image: ghcr.io/blakeblackshear/frigate:stable
container_name: frigate
restart: unless-stopped
privileged: true
shm_size: "512mb"
network_mode: host
environment:
- TZ=America/Chicago
- FRIGATE_RTSP_PASSWORD=password
volumes:
- /mnt/docker-data/configs/frigate/config:/config
- /mnt/frigate-recordings:/media/frigate
- /etc/localtime:/etc/localtime:ro
devices:
- /dev/dri:/dev/dri
mem_limit: 12G
memswap_limit: 12G
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"