diff --git a/Makefile b/Makefile index ff14318..2066a9e 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,11 @@ include $(TOPDIR)/rules.mk +PKG_MAINTAINER:=Jason Hawks + +PKG_NAME:=luci-app-frigate +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 LUCI_TITLE:=LuCI support for frigate LUCI_PKGARCH:=all diff --git a/luasrc/model/cbi/frigate.lua b/luasrc/model/cbi/frigate.lua index 259e136..6a86144 100644 --- a/luasrc/model/cbi/frigate.lua +++ b/luasrc/model/cbi/frigate.lua @@ -20,7 +20,7 @@ s.addremove = false s.anonymous = true o = s:option(Value, "port", "Port") -o.default = "5000" +o.default = "5100" o = s:option(Value, "image", "Image") o.default = "ghcr.io/blakeblackshear/frigate:stable" @@ -46,7 +46,7 @@ o = s:option(Value, "host", "MQTT Host") o.default = "mqtt.server.com" -- Detectors Configuration -s = m:section(NamedSection, "detectors", "frigate_config", "TPU Configuration") +s = m:section(NamedSection, "tpu", "frigate_config", "TPU Configuration") s.addremove = false s.anonymous = true diff --git a/luasrc/view/frigate/status.htm b/luasrc/view/frigate/status.htm index ad9bbaa..df39087 100644 --- a/luasrc/view/frigate/status.htm +++ b/luasrc/view/frigate/status.htm @@ -1,7 +1,7 @@ <% local util = require "luci.util" local container_status = util.trim(util.exec("/usr/libexec/apps/frigate/frigate.sh status")) -local container_running = string.find(string.lower(container_status), "up") and string.find(string.lower(container_status), "(healthy)") +local container_running = string.match(string.lower(container_status), "^up%W") -%>
@@ -16,9 +16,9 @@ local container_running = string.find(string.lower(container_status), "up") and <% if container_running then - local port=util.trim(util.exec("/usr/libexec/apps/frigate/frigate.sh port")) + local port = util.trim(util.exec("/usr/libexec/apps/frigate/frigate.sh port")) if port == "" then - port="1880" + port="5100" end -%>
diff --git a/root/etc/config/frigate b/root/etc/config/frigate index 9977b88..163f1ee 100644 --- a/root/etc/config/frigate +++ b/root/etc/config/frigate @@ -1,16 +1,16 @@ config frigate_config 'docker' - option port '5000' + option port '5100' option image 'ghcr.io/blakeblackshear/frigate:stable' option usbcoral '/dev/bus/usb' option hwaccel '/dev/crypto' option storage './frigate/storage' config frigate_config 'mqtt' - option mqtt '0' + option enabled '0' option host 'mqtt.server.com' -config frigate_config 'detectors' - option coral '1' +config frigate_config 'tpu' + option enabled '1' option type 'edgetpu' option device 'usb' diff --git a/root/usr/libexec/apps/frigate/frigate.sh b/root/usr/libexec/apps/frigate/frigate.sh index 3063f90..97ece0c 100755 --- a/root/usr/libexec/apps/frigate/frigate.sh +++ b/root/usr/libexec/apps/frigate/frigate.sh @@ -10,93 +10,55 @@ get_image() { } do_install_detail() { - local config port IMAGE_NAME username password LAN_IP + local config port IMAGE_NAME LAN_IP local usbcoral hwaccel storage mqtt host coral type device - config=$(uci get frigate.@frigate_config[0].config_path 2>/dev/null) - port=$(uci get frigate.@frigate_config[0].port 2>/dev/null) - IMAGE_NAME=$(uci get frigate.@frigate_config[0].image 2>/dev/null) - usbcoral=$(uci get frigate.@frigate_config[0].usbcoral 2>/dev/null) - hwaccel=$(uci get frigate.@frigate_config[0].hwaccel 2>/dev/null) - storage=$(uci get frigate.@frigate_config[0].storage 2>/dev/null) + echo "Fetching port from configuration..." + port=$(uci get frigate.docker.port 2>/dev/null) + [ -z "$port" ] && { echo "Error: Port not found in configuration."; exit 1; } - mqtt=$(uci get frigate.@frigate_config[0].mqtt 2>/dev/null) - host=$(uci get frigate.@frigate_config[0].host 2>/dev/null) + echo "Fetching image name from configuration..." + IMAGE_NAME=$(uci get frigate.docker.image 2>/dev/null) + [ -z "$IMAGE_NAME" ] && { echo "Error: Image name not found in configuration."; exit 1; } - coral=$(uci get frigate.@frigate_config[0].coral 2>/dev/null) - type=$(uci get frigate.@frigate_config[0].type 2>/dev/null) - device=$(uci get frigate.@frigate_config[0].device 2>/dev/null) + echo "Fetching USB Coral path from configuration..." + usbcoral=$(uci get frigate.docker.usbcoral 2>/dev/null) + [ -z "$usbcoral" ] && { echo "Error: USB Coral path not found in configuration."; exit 1; } - username=$(uci get frigate.@frigate_config[0].username 2>/dev/null) - password=$(uci get frigate.@frigate_config[0].password 2>/dev/null) - APP_NAME="frigate" + echo "Fetching hardware acceleration path from configuration..." + hwaccel=$(uci get frigate.docker.hwaccel 2>/dev/null) + [ -z "$hwaccel" ] && { echo "Error: Hardware acceleration path not found in configuration."; exit 1; } - GEN_PASS=$(< /dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 14; echo) - GEN_PASS2=$(< /dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 14; echo) + echo "Fetching storage path from configuration..." + storage=$(uci get frigate.docker.storage 2>/dev/null) + [ -z "$storage" ] && { echo "Error: Storage path not found in configuration."; exit 1; } - LAN_IP=$(uci get network.lan.ipaddr) - LAN_IP="${LAN_IP%/*}" + echo "Fetching MQTT status from configuration..." + mqtt=$(uci get frigate.mqtt.enabled 2>/dev/null) + [ -z "$mqtt" ] && { echo "Error: MQTT status not found in configuration."; exit 1; } - if [ -z "$config" ]; then - echo "config path is empty!" - exit 1 - fi + echo "Fetching MQTT host from configuration..." + host=$(uci get frigate.mqtt.host 2>/dev/null) + [ -z "$host" ] && { echo "Error: MQTT host not found in configuration."; exit 1; } - [ -z "$port" ] && port=1880 - [ -z "$IMAGE_NAME" ] && IMAGE_NAME="ghcr.io/blakeblackshear/frigate:stable" - - # Initialize counter - local i=0 -mkdir -p /opt/docker2/compose/frigate -touch /opt/docker2/compose/frigate/config.yml - - # Write global settings to config.yml using yq - # Replace these uci get commands with actual commands to get these variables - local usbcoral=$(uci get frigate.@frigate_config[0].usbcoral) - local hwaccel=$(uci get frigate.@frigate_config[0].hwaccel) - local storage=$(uci get frigate.@frigate_config[0].storage) - local mqtt=$(uci get frigate.@frigate_config[0].mqtt) - local host=$(uci get frigate.@frigate_config[0].host) - local coral=$(uci get frigate.@frigate_config[0].coral) - local type=$(uci get frigate.@frigate_config[0].type) - - yq eval ".usbcoral = \"$usbcoral\"" -i /opt/docker2/compose/frigate/config.yml - yq eval ".hwaccel = \"$hwaccel\"" -i /opt/docker2/compose/frigate/config.yml - yq eval ".storage = \"$storage\"" -i /opt/docker2/compose/frigate/config.yml - yq eval ".mqtt.enabled = $mqtt" -i /opt/docker2/compose/frigate/config.yml - yq eval ".mqtt.host = \"$host\"" -i /opt/docker2/compose/frigate/config.yml - yq eval ".detectors.coral.type = \"$type\"" -i /opt/docker2/compose/frigate/config.yml - yq eval ".detectors.coral.device = \"$coral\"" -i /opt/docker2/compose/frigate/config.yml + echo "Fetching Coral status from configuration..." + coral=$(uci get frigate.tpu.device 2>/dev/null) + [ -z "$coral" ] && { echo "Error: Coral status not found in configuration."; exit 1; } - # Write each camera's configuration to config.yml - local camera_index=0 - while : ; do - # Try to fetch camera configuration - local name=$(uci get frigate.@camera_config[$camera_index].name 2>/dev/null) - # Exit loop if no more cameras are found - [[ -z "$name" ]] && break - - # Retrieve the rest of the camera settings here... - local path=$(uci get frigate.@camera_config[$camera_index].path) - local roles=$(uci get frigate.@camera_config[$camera_index].roles) - local record=$(uci get frigate.@camera_config[$camera_index].record) - local snapshots=$(uci get frigate.@camera_config[$camera_index].snapshots) - local mask=$(uci get frigate.@camera_config[$camera_index].mask) - - # Write to config.yml using yq - yq eval ".cameras.$name.ffmpeg.inputs[0].path = \"$path\"" -i /opt/docker2/compose/frigate/config.yml - yq eval ".cameras.$name.ffmpeg.inputs[0].roles[0] = \"detect\"" -i /opt/docker2/compose/frigate/config.yml - yq eval ".cameras.$name.detect.width = 1280" -i /opt/docker2/compose/frigate/config.yml - yq eval ".cameras.$name.detect.height = 720" -i /opt/docker2/compose/frigate/config.yml - yq eval ".cameras.$name.record.enabled = $record" -i /opt/docker2/compose/frigate/config.yml - yq eval ".cameras.$name.snapshots.enabled = $snapshots" -i /opt/docker2/compose/frigate/config.yml - yq eval ".cameras.$name.motion.mask[0] = \"$mask\"" -i /opt/docker2/compose/frigate/config.yml - - camera_index=$((camera_index+1)) - done + echo "Fetching Coral type from configuration..." + type=$(uci get frigate.tpu.type 2>/dev/null) + [ -z "$type" ] && { echo "Error: Coral type not found in configuration."; exit 1; } + echo "Fetching Coral device from configuration..." + device=$(uci get frigate.tpu.device 2>/dev/null) + [ -z "$device" ] && { echo "Error: Coral device not found in configuration."; exit 1; } + LAN_IP=$(uci get network.lan.ipaddr) + LAN_IP="${LAN_IP%/*}" + [ -z "$port" ] && port=1880 + [ -z "$IMAGE_NAME" ] && IMAGE_NAME="ghcr.io/blakeblackshear/frigate:stable" + rm -r /opt/docker2/compose/frigate 2>/dev/null mkdir -p /opt/docker2/compose/frigate @@ -133,7 +95,71 @@ services: FRIGATE_RTSP_PASSWORD: "password" EOF - docker-compose -f /opt/docker2/compose/frigate/docker-compose.yml up -d + # Initialize the base structure + echo "{}" > /opt/docker2/compose/frigate/config.yml + + # Set global configurations + # yq eval ".usbcoral = \"$usbcoral\"" -i /opt/docker2/compose/frigate/config.yml + # yq eval ".hwaccel = \"$hwaccel\"" -i /opt/docker2/compose/frigate/config.yml + # yq eval ".media.storage = \"$storage\"" -i /opt/docker2/compose/frigate/config.yml + yq eval ".mqtt.enabled = $mqtt" -i /opt/docker2/compose/frigate/config.yml + yq eval ".mqtt.host = \"$host\"" -i /opt/docker2/compose/frigate/config.yml + yq eval ".detectors.coral.type = \"$type\"" -i /opt/docker2/compose/frigate/config.yml + yq eval ".detectors.coral.device = \"$coral\"" -i /opt/docker2/compose/frigate/config.yml + + # Initialize cameras structure + yq eval ".cameras = {}" -i /opt/docker2/compose/frigate/config.yml + + # Write each camera's configuration to config.yml + local camera_index=0 + while : ; do + # Try to fetch camera configuration + local name=$(uci get frigate.@camera_config[$camera_index].name 2>/dev/null | tr ' ' '_') + # Exit loop if no more cameras are found + [[ -z "$name" ]] && break + + # Retrieve the rest of the camera settings here... + local path=$(uci get frigate.@camera_config[$camera_index].path 2>/dev/null) + local roles=$(uci get frigate.@camera_config[$camera_index].roles 2>/dev/null) + local record=$(uci get frigate.@camera_config[$camera_index].record 2>/dev/null) + local snapshots=$(uci get frigate.@camera_config[$camera_index].snapshots 2>/dev/null) + local mask=$(uci get frigate.@camera_config[$camera_index].mask 2>/dev/null) + + # Initialize camera structure + yq eval ".cameras.$name = {}" -i /opt/docker2/compose/frigate/config.yml + + # Initialize ffmpeg and inputs for each camera + yq eval ".cameras.$name.ffmpeg = {}" -i /opt/docker2/compose/frigate/config.yml + yq eval ".cameras.$name.ffmpeg.inputs = []" -i /opt/docker2/compose/frigate/config.yml + + # Add path and roles to the inputs + yq eval ".cameras.$name.ffmpeg.inputs[0].path = \"$path\"" -i /opt/docker2/compose/frigate/config.yml + yq eval ".cameras.$name.ffmpeg.inputs[0].roles = [\"detect\"]" -i /opt/docker2/compose/frigate/config.yml + + # Add other camera settings + yq eval ".cameras.$name.detect = {}" -i /opt/docker2/compose/frigate/config.yml + yq eval ".cameras.$name.detect.width = 1280" -i /opt/docker2/compose/frigate/config.yml + yq eval ".cameras.$name.detect.height = 720" -i /opt/docker2/compose/frigate/config.yml + + yq eval ".cameras.$name.record = {}" -i /opt/docker2/compose/frigate/config.yml + yq eval ".cameras.$name.record.enabled = $record" -i /opt/docker2/compose/frigate/config.yml + + yq eval ".cameras.$name.snapshots = {}" -i /opt/docker2/compose/frigate/config.yml + yq eval ".cameras.$name.snapshots.enabled = $snapshots" -i /opt/docker2/compose/frigate/config.yml + + # Check if the mask is not empty + if [ -n "$mask" ]; then + # Run the yq commands to update the YAML file + yq eval ".cameras.$name.motion = {}" -i /opt/docker2/compose/frigate/config.yml + yq eval ".cameras.$name.motion.mask = [\"$mask\"]" -i /opt/docker2/compose/frigate/config.yml + else + echo "Mask is empty, ignoring." + fi + + camera_index=$((camera_index+1)) + done + + docker-compose -f /opt/docker2/compose/frigate/docker-compose.yml up -d --force-recreate uci add shortcutmenu lists uci set shortcutmenu.@lists[-1].webname="$APP_NAME" @@ -169,29 +195,26 @@ case "${ACTION}" in rm -r /opt/docker2/compose/frigate 2>/dev/null ;; "start" | "stop" | "restart") - APP_NAME="frigate" - CONTAINER_IDS=$(docker ps -a --filter "ancestor=${APP_NAME}" --format '{{.ID}}') + CONTAINER_IDS=$(docker ps -a --filter "name=${APP_NAME}" --format '{{.ID}}') for ID in $CONTAINER_IDS; do docker "${ACTION}" "${ID}" done ;; "status") - APP_NAME="frigate" CONTAINER_NAME=$(docker ps -a --filter "name=${APP_NAME}" --format '{{.Names}}') CONTAINER_STATUS=$(docker ps --all --filter "name=${CONTAINER_NAME}" --format '{{.Status}}' | awk '/^Up/ { print "up " substr($0, 4) } !/^Up/ && /.+/ { print "down" }') - if [ -z "$CONTAINER_STATUS" ]; then - echo "${APP_NAME} is not installed" + if [ -z "$CONTAINER_NAME" ]; then + echo "${APP_NAME} is not installed" else echo "${CONTAINER_STATUS}" fi ;; "port") - APP_NAME="frigate" - CONTAINER_NAMES=$(docker ps -a --filter "ancestor=${APP_NAME}" --format '{{.Names}}') - docker ps --all -f "name=${CONTAINER_NAMES}" --format '{{.Ports}}' | grep -o -m1 '0.0.0.0:[0-9]*' | sed 's/0.0.0.0://' - ;; - *) - usage - exit 1 + CONTAINER_NAME=$(docker ps -a --filter "name=${APP_NAME}" --format '{{.Names}}') + # Fetch the port from UCI configuration + uci_port=$(uci get frigate.docker.port 2>/dev/null) + [ -z "$uci_port" ] && exit 1 # Exit silently if UCI port is not found + # Use the UCI port to filter the docker ps output and return only the external port + docker ps --all -f "name=${CONTAINER_NAME}" --format '{{.Ports}}' | grep -o "0.0.0.0:$uci_port->[0-9]*/tcp" | sed "s/0.0.0.0://;s/->[0-9]*\/tcp//" ;; esac \ No newline at end of file