You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

383 lines
13 KiB
Bash

1 year ago
#!/bin/sh
APP_NAME="frigate"
ACTION="${1}"
shift 1
12 months ago
#set -x
1 year ago
fetch_and_validate_uci () {
local config_name=$1
local config_attr=$2
local result=$(uci get frigate.$config_name.$config_attr 2>/dev/null)
if [ -z "$result" ]; then
12 months ago
echo "Error: $config_name not found in configuration." >&2
exit 1
fi
12 months ago
echo "Fetched $config_name from configuration: $result" >&2
echo $result
}
1 year ago
do_install_detail() {
local config port IMAGE_NAME LAN_IP
1 year ago
local usbcoral hwaccel storage mqtt host coral type device
echo "Fetching port from configuration..."
port=$(fetch_and_validate_uci docker port)
echo "Fetching image name from configuration..."
IMAGE_NAME=$(fetch_and_validate_uci docker image)
1 year ago
echo "Fetching USB Coral path from configuration..."
usbcoral=$(fetch_and_validate_uci docker usbcoral)
1 year ago
echo "Fetching hardware acceleration path from configuration..."
hwaccel=$(fetch_and_validate_uci docker hwaccel)
1 year ago
echo "Fetching storage path from configuration..."
storage=$(fetch_and_validate_uci docker storage)
echo "Fetching MQTT status from configuration..."
mqtt=$(fetch_and_validate_uci mqtt enabled)
echo "Fetching MQTT host from configuration..."
host=$(fetch_and_validate_uci mqtt host)
echo "Fetching Coral status from configuration..."
coral=$(fetch_and_validate_uci tpu device)
echo "Fetching Coral type from configuration..."
type=$(fetch_and_validate_uci tpu type)
echo "Fetching Coral device from configuration..."
device=$(fetch_and_validate_uci tpu device)
1 year ago
12 months ago
LAN_IP=$(uci get network.lan.ipaddr)
1 year ago
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
1 year ago
touch /opt/docker2/compose/frigate/config.yml
1 year ago
touch /opt/docker2/compose/frigate/docker-compose.yml
1 year ago
cat > /opt/docker2/compose/frigate/docker-compose.yml <<EOF
version: "3.9"
services:
frigate:
container_name: $APP_NAME
privileged: true # this may not be necessary for all setups
restart: unless-stopped
image: $IMAGE_NAME
shm_size: "64mb" # update for your cameras based on calculation above
devices:
- $usbcoral:/dev/bus/usb # passes the USB Coral, needs to be modified for other versions
#- /dev/apex_0:/dev/apex_0 # passes a PCIe Coral, follow driver instructions here https://coral.ai/docs/m2/get-started/#2a-on-linux
- $hwaccel # for intel hwaccel, needs to be updated for your hardware
volumes:
#- ./frigate/etc/localtime:/etc/localtime:ro
- ./config.yml:/config/config.yml
- $storage:/media/frigate
#- type: tmpfs # Optional: 1GB of memory, reduces SSD/SD Card wear
# target: /tmp/cache
# tmpfs:
# size: 1000000000
ports:
- "$port:5000"
- "8554:8554" # RTSP feeds
- "8555:8555/tcp" # WebRTC over tcp
- "8555:8555/udp" # WebRTC over udp
environment:
FRIGATE_RTSP_PASSWORD: "password"
EOF
# 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
# 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
write_camera_to_yml $name
camera_index=$((camera_index+1))
done
docker-compose -f /opt/docker2/compose/frigate/docker-compose.yml up -d --force-recreate
1 year ago
uci add shortcutmenu lists
uci set shortcutmenu.@lists[-1].webname="$APP_NAME"
uci set shortcutmenu.@lists[-1].weburl="$LAN_IP:$port"
uci set shortcutmenu.@lists[-1].webpath="/"
uci commit shortcutmenu
}
1 year ago
usage() {
echo "usage: $0 sub-command"
echo "where sub-command is one of:"
echo " install Install frigate"
echo " upgrade Upgrade frigate"
echo " rm/start/stop/restart Remove/Start/Stop/Restart frigate"
echo " status frigate status"
echo " port frigate port"
}
# Gets a specific setting for a camera. If no setting is provided, it retrieves the camera name.
get_uci_camera_value() {
local index=$1
local setting=${2:-name}
uci get frigate.@camera_config[$index].$setting 2>/dev/null | tr ' ' '_'
}
# Sets a specific setting for a camera
set_uci_camera_value() {
local name=$1
local setting=$2
local value=$3
uci set frigate.@camera_config[@$name].$setting=$value
uci commit frigate
}
# YML Helper Functions
# Retrieves a specific setting from the YML. If no setting_path is provided, it retrieves the camera name.
get_yml_value() {
local name=$1
local setting_path=${2:-}
yq eval ".cameras.$name$setting_path" /opt/docker2/compose/frigate/config.yml
}
# Sets a specific setting in the YML
set_yml_value() {
local name=$1
local setting_path=$2
local value=$3
# If the value starts with "{", "[", or is a number, we don't quote it; otherwise, we do.
case "$value" in
"{"*|"["*|*[0-9]*)
# do nothing
;;
*)
value="\"$value\""
;;
esac
yq eval ".cameras.$name$setting_path = $value" -i /opt/docker2/compose/frigate/config.yml
}
write_camera_to_yml() {
local name=$1
local camera_index=""
local lines=$(uci show frigate | grep '@camera_config')
IFS=$'\n'
for line in $lines
do
if [[ $line == *".name='$name'"* ]]
then
camera_index=$(echo $line | grep -E -o "@camera_config\[[0-9]+\]" | grep -E -o "[0-9]+" )
break
fi
done
if [[ -z "$camera_index" ]]
then
echo "Camera $name not found in UCI"
return
fi
# Retrieves the rest of the camera settings from UCI...
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 in YML
yq eval ".cameras.$name = {}" -i /opt/docker2/compose/frigate/config.yml
# Initialize ffmpeg and inputs for each camera in YML
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 in YML
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 to YML
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
}
get_camera_index_by_name() {
local name=$1
local camera_index=0
while : ; do
local current_name=$(get_uci_camera_value $camera_index)
[[ "$current_name" == "$name" ]] && echo $camera_index && break
[[ -z "$current_name" ]] && break
camera_index=$((camera_index+1))
done
}
# Toggles the line comment for a specific camera attribute in the YML
toggle_override() {
local name=$1
local attribute=$2
local current_comment=$(yq eval ".cameras.$name.$attribute | line_comment" /opt/docker2/compose/frigate/config.yml)
if [ "$current_comment" = "OVERWRITTEN_BY_UCI" ]; then
# If the current comment is "OVERWRITTEN_BY_UCI", remove it by setting it to nothing
yq eval ".cameras.$name.$attribute line_comment=\"\"" -i /opt/docker2/compose/frigate/config.yml
else
# Otherwise, set the comment to "OVERWRITTEN_BY_UCI"
yq eval ".cameras.$name.$attribute line_comment=\"OVERWRITTEN_BY_UCI\"" -i /opt/docker2/compose/frigate/config.yml
fi
}
update_camera_in_yml() {
local name=$1
# Get the index of the camera with name "$name"
local camera_index=$(get_camera_index_by_name $name)
12 months ago
# Fetch and Update the specific settings for this camera from UCI -> YAML
local path=$(get_uci_camera_value $camera_index "path")
12 months ago
set_yml_value $name ".ffmpeg.inputs[0].path" "\"$path\""
local roles=$(get_uci_camera_value $camera_index "roles")
set_yml_value $name ".ffmpeg.inputs[0].roles" $roles
local record=$(get_uci_camera_value $camera_index "record")
set_yml_value $name ".record.enabled" $record
local snapshots=$(get_uci_camera_value $camera_index "snapshots")
set_yml_value $name ".snapshots.enabled" $snapshots
local mask=$(get_uci_camera_value $camera_index "mask")
if [ -n "$mask" ]; then
12 months ago
set_yml_value $name ".motion.mask" "[\"$mask\"]"
else
yq eval "del(.cameras.$name.motion)" -i /opt/docker2/compose/frigate/config.yml
fi
}
12 months ago
sync_camera_config() {
echo "Syncing camera config..."
# Initializing the camera index
camera_index=0
# Fetching all cameras from frigate YML
yml_cameras=$(yq eval '.cameras | keys' /opt/docker2/compose/frigate/config.yml | sed 's/- //g' | xargs)
echo "Cameras fetched from YML: $yml_cameras"
# Loop through all camera configs in UCI using index
while true; do
# Try to fetch camera configuration
local camera_name=$(get_uci_camera_value $camera_index)
# Exit loop if no more cameras are found
[ -z "$camera_name" ] && break
if ! echo "$yml_cameras" | grep -q -w "$camera_name"; then
echo "UCI Camera $camera_name not found in YML, adding..."
write_camera_to_yml $camera_name
else
echo "UCI Camera $camera_name found in YML, updating..."
update_camera_in_yml $camera_name
fi
# Add to uci_cameras
uci_cameras="${uci_cameras} ${camera_name}"
# Increment camera index
camera_index=$((camera_index+1))
done
# Crosscheck and remove any old cameras in YML not present in UCI config
for yml_camera in $yml_cameras; do
# Fix for word splitting
set -f
if ! echo $uci_cameras | grep -q -w "$yml_camera"; then
echo "Camera $yml_camera found in YML, but not in UCI, removing..."
yq eval "del(.cameras.$yml_camera)" -i /opt/docker2/compose/frigate/config.yml
fi
set +f
done
echo "sync_camera_config completed!"
}
1 year ago
case "${ACTION}" in
"install" | "upgrade")
do_install_detail
;;
"rm")
IMAGE_NAME=$(uci get frigate.@frigate[0].image_name 2>/dev/null)
[ -z "$IMAGE_NAME" ] && IMAGE_NAME="ghcr.io/blakeblackshear/frigate:stable"
CONTAINER_IDS=$(docker ps -a --filter "ancestor=${IMAGE_NAME}" --format '{{.ID}}')
echo "Stopping and removing containers..."
for ID in $CONTAINER_IDS; do
docker stop "$ID"
docker rm "$ID"
done
docker rmi -f "$IMAGE_NAME"
rm -r /opt/docker2/compose/frigate 2>/dev/null
;;
"start" | "stop" | "restart")
CONTAINER_IDS=$(docker ps -a --filter "name=${APP_NAME}" --format '{{.ID}}')
1 year ago
for ID in $CONTAINER_IDS; do
docker "${ACTION}" "${ID}"
sync_camera_config
1 year ago
done
;;
"status")
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_NAME" ]; then
echo "${APP_NAME} is not installed"
else
echo "${CONTAINER_STATUS}"
fi
;;
"port")
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//"
1 year ago
;;
esac