mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-11-14 17:43:01 +03:00
The frigate and go2rtc run scripts probed "$ffmpeg_path" -version as root before dropping privileges, and resolve_ffmpeg_path only refused runtime-user-writable paths when frigate itself was a root service. In the default mode uid 1000 could set ffmpeg.path to a /config directory, plant bin/ffmpeg there, and get root on the next restart, and under FRIGATE_ROOT_SERVICES=go2rtc a root go2rtc used that path as its ffmpeg bin. The probe now runs as the service user whenever the service drops privileges, and resolve_ffmpeg_path refuses writable paths for whichever service will run the binary, returning the resolved path so a symlink can't be retargeted after the check. go2rtc falls back to the bundled build's libavformat version if its user can't run the custom build.
125 lines
4.9 KiB
Plaintext
Executable File
125 lines
4.9 KiB
Plaintext
Executable File
#!/command/with-contenv bash
|
|
# shellcheck shell=bash
|
|
# Start the go2rtc service
|
|
|
|
set -o errexit -o nounset -o pipefail
|
|
|
|
runs_as_root=0
|
|
if [[ "$(id -u)" -eq 0 ]]; then
|
|
if [[ "${FRIGATE_RUN_AS_ROOT:-false}" == "true" ]] || /usr/local/bin/service-runs-as-root go2rtc; then
|
|
runs_as_root=1
|
|
fi
|
|
fi
|
|
|
|
# Root via FRIGATE_ROOT_SERVICES only; the escape hatch sweeps nothing and
|
|
# leaves no unprivileged service, so /config/go2rtc stays as safe as pre-drop.
|
|
granular_root=0
|
|
if [[ "$runs_as_root" -eq 1 && "${FRIGATE_RUN_AS_ROOT:-false}" != "true" ]]; then
|
|
granular_root=1
|
|
fi
|
|
|
|
# Logs should be sent to stdout so that s6 can collect them
|
|
|
|
function get_ip_and_port_from_supervisor() {
|
|
local ip_address
|
|
# Example: 192.168.1.10/24
|
|
local ip_regex='^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/[0-9]{1,2}$'
|
|
if ip_address=$(
|
|
curl -fsSL \
|
|
-H "Authorization: Bearer ${SUPERVISOR_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
http://supervisor/network/interface/default/info |
|
|
jq --exit-status --raw-output '.data.ipv4.address[0]'
|
|
) && [[ "${ip_address}" =~ ${ip_regex} ]]; then
|
|
ip_address="${BASH_REMATCH[1]}"
|
|
echo "[INFO] Got IP address from supervisor: ${ip_address}"
|
|
else
|
|
echo "[WARN] Failed to get IP address from supervisor"
|
|
return 0
|
|
fi
|
|
|
|
local webrtc_port
|
|
local port_regex='^([0-9]{1,5})$'
|
|
if webrtc_port=$(
|
|
curl -fsSL \
|
|
-H "Authorization: Bearer ${SUPERVISOR_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
http://supervisor/addons/self/info |
|
|
jq --exit-status --raw-output '.data.network["8555/tcp"]'
|
|
) && [[ "${webrtc_port}" =~ ${port_regex} ]]; then
|
|
webrtc_port="${BASH_REMATCH[1]}"
|
|
echo "[INFO] Got WebRTC port from supervisor: ${webrtc_port}"
|
|
else
|
|
echo "[WARN] Failed to get WebRTC port from supervisor"
|
|
return 0
|
|
fi
|
|
|
|
export FRIGATE_GO2RTC_WEBRTC_CANDIDATE_INTERNAL="${ip_address}:${webrtc_port}"
|
|
}
|
|
|
|
# The probe runs as the service user when the service drops privileges; a root
|
|
# probe relies on resolve_ffmpeg_path refusing runtime-user-writable paths
|
|
function set_libva_version() {
|
|
local ffmpeg_path
|
|
local probe_as=()
|
|
if [[ "$(id -u)" -eq 0 && "$runs_as_root" -eq 0 ]]; then
|
|
probe_as=(s6-setuidgid go2rtc)
|
|
fi
|
|
ffmpeg_path=$(python3 -I /usr/local/ffmpeg/get_ffmpeg_path.py go2rtc)
|
|
if ! LIBAVFORMAT_VERSION_MAJOR=$("${probe_as[@]}" "$ffmpeg_path" -version | grep -Po "libavformat\W+\K\d+"); then
|
|
echo "[WARN] Could not read the libavformat version from ${ffmpeg_path} as the go2rtc user; using the bundled build's version"
|
|
LIBAVFORMAT_VERSION_MAJOR=$("${probe_as[@]}" "/usr/lib/ffmpeg/${DEFAULT_FFMPEG_VERSION}/bin/ffmpeg" -version | grep -Po "libavformat\W+\K\d+")
|
|
fi
|
|
export LIBAVFORMAT_VERSION_MAJOR
|
|
}
|
|
|
|
set_libva_version
|
|
|
|
# create_config.py creates the file exclusively and fails if anything
|
|
# reappears at this path
|
|
rm -f /dev/shm/go2rtc.yaml
|
|
|
|
echo "[INFO] Preparing new go2rtc config..."
|
|
|
|
if [[ -n "${SUPERVISOR_TOKEN:-}" ]]; then
|
|
# Running as a Home Assistant Add-on, infer the IP address and port
|
|
get_ip_and_port_from_supervisor
|
|
fi
|
|
|
|
python3 -I /usr/local/go2rtc/create_config.py
|
|
|
|
# HomeKit persistence. The helper is symlink-safe; hand off to go2rtc only when dropping.
|
|
readonly homekit_config_path="/config/go2rtc_homekit.yml"
|
|
if [[ "$(id -u)" -eq 0 && "$runs_as_root" -eq 0 ]]; then
|
|
python3 -I /usr/local/go2rtc/prepare_homekit.py "${homekit_config_path}" --chown
|
|
chown -h go2rtc:go2rtc /dev/shm/go2rtc.yaml 2>/dev/null || true
|
|
else
|
|
python3 -I /usr/local/go2rtc/prepare_homekit.py "${homekit_config_path}"
|
|
fi
|
|
|
|
readonly config_path="/config"
|
|
|
|
# the sweep hands /config to uid 1000, so a root service must not exec from it
|
|
if [[ "$granular_root" -eq 1 && -x "${config_path}/go2rtc" ]]; then
|
|
echo "[WARN] Ignoring '${config_path}/go2rtc' because FRIGATE_ROOT_SERVICES runs this service as root and /config is owned by the runtime user; using the embedded binary"
|
|
echo "[WARN] Use FRIGATE_RUN_AS_ROOT=true instead if you need both a custom go2rtc build and root"
|
|
readonly binary_path="/usr/local/go2rtc/bin/go2rtc"
|
|
elif [[ -x "${config_path}/go2rtc" ]]; then
|
|
readonly binary_path="${config_path}/go2rtc"
|
|
echo "[WARN] Using go2rtc binary from '${binary_path}' instead of the embedded one"
|
|
else
|
|
readonly binary_path="/usr/local/go2rtc/bin/go2rtc"
|
|
fi
|
|
|
|
echo "[INFO] Starting go2rtc..."
|
|
|
|
# Replace the bash process with the go2rtc process, redirecting stderr to stdout
|
|
# Use HomeKit config as the primary config so writebacks go there
|
|
# The main config from Frigate will be loaded as a secondary config
|
|
exec 2>&1
|
|
if [[ "$(id -u)" -ne 0 || "$runs_as_root" -eq 1 ]]; then
|
|
exec "${binary_path}" -config="${homekit_config_path}" -config=/dev/shm/go2rtc.yaml
|
|
else
|
|
exec s6-setuidgid go2rtc "${binary_path}" -config="${homekit_config_path}" -config=/dev/shm/go2rtc.yaml
|
|
fi
|