diff --git a/birdnet-pipy/Dockerfile b/birdnet-pipy/Dockerfile index 38bf6249f..2e4c60451 100644 --- a/birdnet-pipy/Dockerfile +++ b/birdnet-pipy/Dockerfile @@ -50,7 +50,7 @@ USER root # Copy local files COPY rootfs/ / -RUN find . -type f \( -name "*.sh" -o -name "run" \) -print -exec chmod +x {} \; +RUN find /etc -type f \( -name "*.sh" -o -path "*/services.d/*/run" \) -exec chmod +x {} \; # Uses /bin for compatibility purposes # hadolint ignore=DL4005 diff --git a/birdnet-pipy/config.yaml b/birdnet-pipy/config.yaml index 5182f6578..f38ff87d5 100644 --- a/birdnet-pipy/config.yaml +++ b/birdnet-pipy/config.yaml @@ -11,8 +11,10 @@ ingress_entry: / ingress_stream: true panel_icon: mdi:bird init: false +share: true map: - addon_config:rw + - data:rw ports: 8099/tcp: 8099 ports_description: @@ -21,11 +23,17 @@ options: TZ: Etc/UTC ICECAST_PASSWORD: "" STREAM_BITRATE: 320k + RECORDING_MODE: "rtsp" + RTSP_URL: "" + data_location: /config/data env_vars: [] schema: TZ: str? ICECAST_PASSWORD: str? STREAM_BITRATE: str? + RECORDING_MODE: list(pulseaudio|http_stream|rtsp) + RTSP_URL: str? + data_location: str env_vars: - name: match(^[A-Za-z0-9_]+$) value: str? diff --git a/birdnet-pipy/rootfs/etc/cont-init.d/01-structure.sh b/birdnet-pipy/rootfs/etc/cont-init.d/01-structure.sh index 0f5ab5d33..6c102d648 100755 --- a/birdnet-pipy/rootfs/etc/cont-init.d/01-structure.sh +++ b/birdnet-pipy/rootfs/etc/cont-init.d/01-structure.sh @@ -1,24 +1,37 @@ #!/usr/bin/with-contenv bashio # shellcheck shell=bash -set -e +set -euo pipefail -DATA_ROOT="/config/birdnet-pipy" -DATA_DIR="${DATA_ROOT}/data" +DEFAULT_LOCATION="/config/data" +DATA_LOCATION="$(bashio::config 'data_location' || true)" +DATA_LOCATION="${DATA_LOCATION:-$DEFAULT_LOCATION}" -mkdir -p "${DATA_DIR}" +case "${DATA_LOCATION}" in + /config/*|/share/*|/data/*) ;; + *) + bashio::log.warning "Invalid data_location '${DATA_LOCATION}', falling back to ${DEFAULT_LOCATION}" + DATA_LOCATION="${DEFAULT_LOCATION}" + ;; +esac -if [ -e /app/data ] && [ ! -L /app/data ]; then - rm -rf /app/data +LEGACY1="/config/birdnet-pipy/data" +LEGACY2="/data" + +mkdir -p "${DATA_LOCATION}" + +if [ -z "$(ls -A "${DATA_LOCATION}" 2>/dev/null || true)" ]; then + if [ -d "${LEGACY1}" ] && [ -n "$(ls -A "${LEGACY1}" 2>/dev/null || true)" ]; then + bashio::log.notice "Migrating legacy data from ${LEGACY1} to ${DATA_LOCATION}" + cp -a "${LEGACY1}/." "${DATA_LOCATION}/" || true + elif [ -d "${LEGACY2}" ] && [ "${LEGACY2}" != "${DATA_LOCATION}" ] && [ -n "$(ls -A "${LEGACY2}" 2>/dev/null || true)" ]; then + bashio::log.notice "Migrating legacy data from ${LEGACY2} to ${DATA_LOCATION}" + cp -a "${LEGACY2}/." "${DATA_LOCATION}/" || true + fi fi -if [ ! -L /app/data ]; then - ln -s "${DATA_DIR}" /app/data -fi +mkdir -p "${DATA_LOCATION}/config" "${DATA_LOCATION}/clips" "${DATA_LOCATION}/logs" "${DATA_LOCATION}/cache" || true -mkdir -p \ - /app/data/config \ - /app/data/db \ - /app/data/audio/recordings \ - /app/data/audio/extracted_songs \ - /app/data/spectrograms \ - /app/data/flags +rm -rf /app/data +ln -s "${DATA_LOCATION}" /app/data + +bashio::log.notice "Data location set to: ${DATA_LOCATION}" diff --git a/birdnet-pipy/rootfs/etc/cont-init.d/10-config.sh b/birdnet-pipy/rootfs/etc/cont-init.d/10-config.sh new file mode 100644 index 000000000..8b55d869d --- /dev/null +++ b/birdnet-pipy/rootfs/etc/cont-init.d/10-config.sh @@ -0,0 +1,34 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -euo pipefail + +DATA_DIR="/app/data" +CFG_DIR="${DATA_DIR}/config" +SETTINGS="${CFG_DIR}/user_settings.json" + +mkdir -p "${CFG_DIR}" + +if [ ! -f "${SETTINGS}" ]; then + if [ -f /app/config/user_settings.example.json ]; then + cp /app/config/user_settings.example.json "${SETTINGS}" + else + printf '%s\n' '{}' > "${SETTINGS}" + fi +fi + +RECORDING_MODE="$(bashio::config 'RECORDING_MODE' || true)" +RTSP_URL="$(bashio::config 'RTSP_URL' || true)" + +PATCH='{}' +if [ -n "${RECORDING_MODE}" ]; then + PATCH="$(printf '%s' "${PATCH}" | jq --arg v "${RECORDING_MODE}" '.audio.recording_mode=$v')" +fi +if [ -n "${RTSP_URL}" ]; then + PATCH="$(printf '%s' "${PATCH}" | jq --arg v "${RTSP_URL}" '.audio.rtsp_url=$v')" +fi + +tmp="$(mktemp)" +jq -s '.[0] * .[1]' "${SETTINGS}" <(printf '%s\n' "${PATCH}") > "${tmp}" +mv "${tmp}" "${SETTINGS}" + +chmod 0644 "${SETTINGS}" || true diff --git a/birdnet-pipy/rootfs/etc/cont-init.d/15-audio_perms.sh b/birdnet-pipy/rootfs/etc/cont-init.d/15-audio_perms.sh new file mode 100644 index 000000000..162fbd319 --- /dev/null +++ b/birdnet-pipy/rootfs/etc/cont-init.d/15-audio_perms.sh @@ -0,0 +1,21 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -euo pipefail + +SND_GID="" +if [ -e /dev/snd ] && command -v stat >/dev/null 2>&1; then + SND_GID="$(stat -c '%g' /dev/snd 2>/dev/null || true)" +fi + +if [ -n "${SND_GID}" ] && getent group audio >/dev/null 2>&1; then + current_gid="$(getent group audio | cut -d: -f3 || true)" + if [ -n "${current_gid}" ] && [ "${current_gid}" != "${SND_GID}" ]; then + groupmod -g "${SND_GID}" audio 2>/dev/null || true + fi +fi + +for u in root nginx www-data; do + if id "${u}" >/dev/null 2>&1; then + usermod -aG audio "${u}" 2>/dev/null || true + fi +done diff --git a/birdnet-pipy/rootfs/etc/cont-init.d/99-run.sh b/birdnet-pipy/rootfs/etc/cont-init.d/99-run.sh deleted file mode 100755 index ed099d7dd..000000000 --- a/birdnet-pipy/rootfs/etc/cont-init.d/99-run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/with-contenv bashio -# shellcheck shell=bash -set -e - -export PYTHONPATH=/app -export PULSE_SERVER=unix:/run/pulse/native - -cd /app - -bashio::log.info "Starting BirdNET-PiPy services" - -python3 -m model_service.inference_server & -python3 -m core.api & -python3 -m core.main & - -/usr/local/bin/start-icecast.sh & - -bashio::net.wait_for 5002 localhost 300 -bashio::log.info "BirdNET-PiPy API is available" - -exec nginx diff --git a/birdnet-pipy/rootfs/etc/services.d/api/run b/birdnet-pipy/rootfs/etc/services.d/api/run new file mode 100644 index 000000000..d525a36ed --- /dev/null +++ b/birdnet-pipy/rootfs/etc/services.d/api/run @@ -0,0 +1,7 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -euo pipefail +export PYTHONPATH=/app +bashio::net.wait_for 5001 127.0.0.1 300 +cd /app +exec python3 -m core.api diff --git a/birdnet-pipy/rootfs/etc/services.d/icecast/run b/birdnet-pipy/rootfs/etc/services.d/icecast/run new file mode 100644 index 000000000..2c0779010 --- /dev/null +++ b/birdnet-pipy/rootfs/etc/services.d/icecast/run @@ -0,0 +1,5 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -euo pipefail +export PULSE_SERVER=unix:/run/pulse/native +exec /usr/local/bin/start-icecast.sh diff --git a/birdnet-pipy/rootfs/etc/services.d/main/run b/birdnet-pipy/rootfs/etc/services.d/main/run new file mode 100644 index 000000000..0ceae662a --- /dev/null +++ b/birdnet-pipy/rootfs/etc/services.d/main/run @@ -0,0 +1,8 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -euo pipefail +export PYTHONPATH=/app +export PULSE_SERVER=unix:/run/pulse/native +bashio::net.wait_for 5002 127.0.0.1 300 +cd /app +exec python3 -m core.main diff --git a/birdnet-pipy/rootfs/etc/services.d/model/run b/birdnet-pipy/rootfs/etc/services.d/model/run new file mode 100644 index 000000000..34cc13699 --- /dev/null +++ b/birdnet-pipy/rootfs/etc/services.d/model/run @@ -0,0 +1,6 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -euo pipefail +export PYTHONPATH=/app +cd /app +exec python3 -m model_service.inference_server diff --git a/birdnet-pipy/rootfs/etc/services.d/nginx/run b/birdnet-pipy/rootfs/etc/services.d/nginx/run new file mode 100644 index 000000000..1390bdbb4 --- /dev/null +++ b/birdnet-pipy/rootfs/etc/services.d/nginx/run @@ -0,0 +1,4 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -euo pipefail +exec nginx -g "daemon off;"