mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-01 10:50:32 +02:00
Add birdnet-pi-zach addon (zach7036/BirdNET-Pi-Enhanced-Version fork)
Creates a new Home Assistant add-on that installs from zach7036's BirdNET-Pi-Enhanced-Version fork instead of the Nachtzuster/alexbelgium fork. Leaves the existing birdnet-pi addon untouched.
Key changes from birdnet-pi:
- Installer repointed to zach7036/BirdNET-Pi-Enhanced-Version/main/newinstaller.sh
- Removed alexbelgium repo rename sed (zach7036 fork already clones itself)
- Removed merge_open_prs PR-merging step (install stable main)
- Updated slug to birdnet-pi-zach, image to birdnet-pi-zach-{arch}
- Updated README/docs upstream description
- Unique apparmor profile name
The generic Dockerfile patches (strip sudo, remap my_dir, systemctl shims) apply unchanged to the new fork since it follows standard BirdNET-Pi layout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2
birdnet-pi-zach/CHANGELOG.md
Normal file
2
birdnet-pi-zach/CHANGELOG.md
Normal file
@@ -0,0 +1,2 @@
|
||||
## 2026.07.10 (10-07-2026)
|
||||
- Initial release: BirdNET-Pi add-on based on the zach7036/BirdNET-Pi-Enhanced-Version fork
|
||||
283
birdnet-pi-zach/DOCS.md
Normal file
283
birdnet-pi-zach/DOCS.md
Normal file
@@ -0,0 +1,283 @@
|
||||
# Microphone considerations
|
||||
|
||||
The critical element is the microphone quality: a Boya By-lm 40 or Clippy EM272 (with a very good aux-usb converter) is key to improve the quality of detections.
|
||||
|
||||
Here are some example tests I did (the whole threads are really interesting also):
|
||||
|
||||
- <https://github.com/mcguirepr89/BirdNET-Pi/discussions/39#discussioncomment-9706951>
|
||||
- <https://github.com/mcguirepr89/BirdNET-Pi/discussions/1092#discussioncomment-9706191>
|
||||
|
||||
**My recommendation:**
|
||||
|
||||
- **Best entry system (< 50 €):** Boya By-lm40 (30 €) + deadcat (10 €)
|
||||
- **Best middle end system (< 150 €):** Clippy EM272 TRS/TRRS (55 €) + Rode AI micro trs/trrs to usb (70 €) + Rycote deadcat (27 €)
|
||||
- **Best high end system (<400 €):** Clippy EM272 XLR (85 €) or LOM Ucho Pro (75 €) + Focusrite Scarlet 2i2 4th Gen (200 €) + Bubblebee Pro Extreme deadcat (45 €)
|
||||
|
||||
**Sources for high end microphones in Europe:**
|
||||
|
||||
- Clippy (EM272): <https://www.veldshop.nl/en/clippy-xlr-em272z1-mono-microphone.html>
|
||||
- LOM (EM272): <https://store.lom.audio/collections/basicucho-series>
|
||||
- Immersive sound (AOM5024): <https://immersivesoundscapes.com/earsight-standard-v2/>
|
||||
|
||||
# App settings recommendation
|
||||
|
||||
I've tested lots of settings by running 2 versions of my HA birdnet-pi addon in parallel using the same RTSP feed and comparing the impact of parameters. My conclusions aren't universal, as it seems to be highly dependent on the region and type of mic used. For example, the old model seems to be better in Australia, while the new one is better in Europe.
|
||||
|
||||
- **Model**
|
||||
- **Version:** 6k_v2.4 _(performs better in Europe at least, the 6k performs better in Australia)_
|
||||
- **Species range model:** v1 _(uncheck v2.4; seems more robust in Europe)_
|
||||
- **Species occurrence threshold:** 0.001 _(was 0.00015 using v2.4; use the Species List Tester to check the correct value for you)_
|
||||
- **Audio settings**
|
||||
- **Default**
|
||||
- **Channel:** 1 _(doesn't really matter as analysis is made on mono signal; 1 allows decreased saved audio size but seems to give slightly messed up spectrograms in my experience)_
|
||||
- **Recording Length:** 18 _(that's because I use an overlap of 0.5; so it analyzes 0-3s, 2.5-5.5s, 5-8s, 7.5-10.5, 10-13, 12.5-15.5, 15-18)_
|
||||
- **Extraction Length:** 9s _(could be 6, but I like to hear my birds :-))_
|
||||
- **Audio format:** mp3 _(why bother with something else)_
|
||||
- **Birdnet-lite settings**
|
||||
- **Overlap:** 0.5s
|
||||
- **Minimum confidence:** 0.7
|
||||
- **Sigmoid sensitivity:** 1.25 _(I've tried 1.00 but it gave much more false positives; decreasing this value increases sensitivity)_
|
||||
|
||||
# Set RTSP server
|
||||
|
||||
Inspired by: <https://github.com/mcguirepr89/BirdNET-Pi/discussions/1006#discussioncomment-6747450>
|
||||
|
||||
<details>
|
||||
<summary>On your desktop</summary>
|
||||
|
||||
- Download imager
|
||||
- Install raspbian lite 64
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>With ssh, install requisite softwares</summary>
|
||||
|
||||
```bash
|
||||
# Update
|
||||
sudo apt-get update -y
|
||||
sudo apt-get dist-upgrade -y
|
||||
|
||||
# Install RTSP server
|
||||
sudo apt-get install -y micro ffmpeg lsof
|
||||
sudo -s cd /root && wget -c https://github.com/bluenviron/mediamtx/releases/download/v1.18.1/mediamtx_v1.18.1_linux_arm64v8.tar.gz -O - | sudo tar -xz
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Configure Audio</summary>
|
||||
|
||||
### Find right device
|
||||
|
||||
```bash
|
||||
# List audio devices
|
||||
arecord -l
|
||||
|
||||
# Check audio device parameters. Example:
|
||||
arecord -D hw:1,0 --dump-hw-params
|
||||
```
|
||||
|
||||
### Add startup script
|
||||
|
||||
```bash
|
||||
sudo nano startmic.sh && chmod +x startmic.sh
|
||||
```
|
||||
|
||||
Paste the following content:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "Starting birdmic"
|
||||
|
||||
# Disable gigabit ethernet
|
||||
sudo ethtool -s eth0 speed 100 duplex full autoneg on
|
||||
|
||||
# Detect Scarlett 2i2 card index - relevant only if you use that card
|
||||
SCARLETT_INDEX=$(arecord -l | grep -i "Scarlett" | awk '{print $2}' | sed 's/://')
|
||||
|
||||
if [ -z "$SCARLETT_INDEX" ]; then
|
||||
echo "Error: Scarlett 2i2 not found! Using 0 as default"
|
||||
SCARLETT_INDEX="0"
|
||||
fi
|
||||
|
||||
# Start mediamtx first and give it a moment to initialize
|
||||
./mediamtx &
|
||||
sleep 5
|
||||
|
||||
# Run ffmpeg
|
||||
ffmpeg -nostdin -use_wallclock_as_timestamps 1 -fflags +genpts -f alsa -acodec pcm_s16be -ac 2 -ar 96000 -i plughw:$SCARLETT_INDEX,0 -ac 2 -f rtsp -acodec pcm_s16be rtsp://localhost:8554/birdmic -rtsp_transport tcp -buffer_size 512k 2>/tmp/rtsp_error &
|
||||
|
||||
# Set microphone volume
|
||||
sleep 5
|
||||
MICROPHONE_NAME="Line In 1 Gain" # for Focusrite Scarlett 2i2
|
||||
sudo amixer -c 0 sset "$MICROPHONE_NAME" 40
|
||||
|
||||
sleep 60
|
||||
|
||||
# Run focusrite and autogain scripts if present
|
||||
if [ -f "$HOME/focusrite.sh" ]; then
|
||||
sudo python3 -u "$HOME/focusrite.sh" >/tmp/log_focusrite 2>/tmp/log_focusrite_error &
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/autogain.py" ]; then
|
||||
sudo python3 -u "$HOME/autogain.py" >/tmp/log_autogain 2>/tmp/log_autogain_error &
|
||||
fi
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Optional: Startup automatically</summary>
|
||||
|
||||
```bash
|
||||
chmod +x startmic.sh
|
||||
crontab -e # select nano as your editor
|
||||
```
|
||||
Paste in:
|
||||
|
||||
```bash
|
||||
@reboot $HOME/startmic.sh
|
||||
```
|
||||
|
||||
then save and exit nano.
|
||||
Reboot the Pi and test again with VLC to make sure the RTSP stream is live.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Optional: disable unnecessary elements</summary>
|
||||
|
||||
- **Optimize config.txt**
|
||||
|
||||
```bash
|
||||
sudo nano /boot/firmware/config.txt
|
||||
```
|
||||
|
||||
Paste in:
|
||||
|
||||
```ini
|
||||
# ── Audio ──────────────────────────────────────────────────────
|
||||
dtparam=audio=off
|
||||
|
||||
# ── Disable radios (RF + background jitter) ─────────────────────
|
||||
dtoverlay=disable-wifi
|
||||
dtoverlay=disable-bt
|
||||
|
||||
# ── Headless / minimal firmware probing ─────────────────────────
|
||||
gpu_mem=16
|
||||
start_x=0
|
||||
camera_auto_detect=0
|
||||
display_auto_detect=0
|
||||
disable_splash=1
|
||||
|
||||
# ── Optional: only effective on newer Pi4B revs / Pi 400 ────────
|
||||
arm_boost=1
|
||||
hdmi_blanking=2 # Disable HDMI (save power and reduce interference)
|
||||
```
|
||||
|
||||
- **Optimize cmdline.txt**
|
||||
|
||||
```bash
|
||||
sudo nano /boot/firmware/cmdline.txt
|
||||
```
|
||||
|
||||
Paste in:
|
||||
|
||||
```ini
|
||||
console=serial0,115200 console=tty1 root=PARTUUID=2f0ecb16-02 rootfstype=ext4 fsck.repair=yes rootwait cfg80211.ieee80211_regdom=FI dwc_otg.fiq_enable=0 dwc_otg.fiq_split_enable=0 dwc_otg.fiq_fsm_enable=0 dwc_otg.lpm_enable=0 usbcore.autosuspend=-1 snd_usb_audio.nrpacks=1 threadirqs
|
||||
```
|
||||
|
||||
- **Disable useless services**
|
||||
|
||||
```bash
|
||||
# Disable useless services
|
||||
sudo systemctl disable hciuart
|
||||
sudo systemctl disable bluetooth
|
||||
sudo systemctl disable triggerhappy
|
||||
sudo systemctl disable avahi-daemon
|
||||
sudo systemctl disable dphys-swapfile
|
||||
sudo systemctl disable hciuart.service
|
||||
|
||||
# Disable bluetooth
|
||||
for element in bluetooth btbcm hci_uart btintel btrtl btusb; do
|
||||
sudo sed -i "/$element/d" /etc/modprobe.d/raspi-blacklist.conf
|
||||
echo "blacklist $element" | sudo tee -a /etc/modprobe.d/raspi-blacklist.conf
|
||||
done
|
||||
|
||||
# Disable Video (Including V4L2) on Your Raspberry Pi
|
||||
for element in bcm2835_v4l2 bcm2835_codec bcm2835_isp videobuf2_vmalloc videobuf2_memops videobuf2_v4l2 videobuf2_common videodev; do
|
||||
sudo sed -i "/$element/d" /etc/modprobe.d/raspi-blacklist.conf
|
||||
echo "blacklist $element" | sudo tee -a /etc/modprobe.d/raspi-blacklist.conf
|
||||
done
|
||||
|
||||
# Disable WiFi Power Management
|
||||
sudo iw dev wlan0 set power_save off
|
||||
for element in brcmfmac brcmutil; do
|
||||
sudo sed -i "/$element/d" /etc/modprobe.d/raspi-blacklist.conf
|
||||
echo "blacklist $element" | sudo tee -a /etc/modprobe.d/raspi-blacklist.conf
|
||||
done
|
||||
|
||||
# Disable USB Power Management
|
||||
echo 'on' | sudo tee /sys/bus/usb/devices/usb*/power/control
|
||||
|
||||
# Preventing the Raspberry Pi from Entering Power-Saving Mode
|
||||
sudo apt update
|
||||
sudo apt install -y cpufrequtils
|
||||
echo 'GOVERNOR="performance"' | sudo tee /etc/default/cpufrequtils
|
||||
sudo systemctl disable ondemand
|
||||
sudo systemctl stop ondemand
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Optional: install Focusrite driver</summary>
|
||||
|
||||
```bash
|
||||
sudo apt-get install make linux-headers-$(uname -r)
|
||||
curl -LO https://github.com/geoffreybennett/scarlett-gen2/releases/download/v6.9-v1.3/snd-usb-audio-kmod-6.6-v1.3.tar.gz
|
||||
tar -xzf snd-usb-audio-kmod-6.6-v1.3.tar.gz
|
||||
cd snd-usb-audio-kmod-6.6-v1.3
|
||||
KSRCDIR=/lib/modules/$(uname -r)/build
|
||||
make -j4 -C $KSRCDIR M=$(pwd) clean
|
||||
make -j4 -C $KSRCDIR M=$(pwd)
|
||||
sudo make -j4 -C $KSRCDIR M=$(pwd) INSTALL_MOD_DIR=updates/snd-usb-audio modules_install
|
||||
sudo depmod
|
||||
sudo reboot
|
||||
dmesg | grep -A 5 -B 5 -i focusrite
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Optional: add RAM disk</summary>
|
||||
|
||||
```bash
|
||||
sudo cp /usr/share/systemd/tmp.mount /etc/systemd/system/tmp.mount
|
||||
sudo systemctl enable tmp.mount
|
||||
sudo systemctl start tmp.mount
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Optional: Configuration for Focusrite Scarlett 2i2</summary>
|
||||
|
||||
Add this content in `$HOME/focusrite.sh` and run:
|
||||
|
||||
```bash
|
||||
chmod +x "$HOME/focusrite.sh"
|
||||
```
|
||||
|
||||
See: <https://github.com/alexbelgium/Birdnet-tools/blob/main/focusrite.sh>
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Optional: Autogain script for microphone</summary>
|
||||
|
||||
Add this content in `$HOME/autogain.py` and run:
|
||||
|
||||
```bash
|
||||
chmod +x "$HOME/autogain.py"
|
||||
```
|
||||
|
||||
See: <https://github.com/alexbelgium/Birdnet-tools/blob/main/autogain.py>
|
||||
|
||||
</details>
|
||||
241
birdnet-pi-zach/Dockerfile
Normal file
241
birdnet-pi-zach/Dockerfile
Normal file
@@ -0,0 +1,241 @@
|
||||
#============================#
|
||||
# ALEXBELGIUM'S DOCKERFILE #
|
||||
#============================#
|
||||
# _.------.
|
||||
# _.-` ('>.-`"""-.port
|
||||
# '.--'` _'` _ .--.)
|
||||
# -' '-.-';` `
|
||||
# ' - _.' ``'--.
|
||||
# '---` .-'""`
|
||||
# /`
|
||||
#=== Home Assistant Addon ===#
|
||||
|
||||
#################
|
||||
# 1 Build Image #
|
||||
#################
|
||||
|
||||
ARG BUILD_VERSION
|
||||
ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
# Install locales
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y locales && locale-gen en_US.UTF-8
|
||||
ENV DEBIAN_FRONTEND="noninteractive" \
|
||||
BIRDNET_USER="pi" \
|
||||
USER="pi" \
|
||||
PUID=1000 \
|
||||
PGID=1000 \
|
||||
HOME="/home/pi" \
|
||||
XDG_RUNTIME_DIR="/run/user/1000" \
|
||||
PYTHON_VIRTUAL_ENV="/home/pi/BirdNET-Pi/birdnet/bin/python3" \
|
||||
my_dir=/home/pi/BirdNET-Pi/scripts \
|
||||
LANGUAGE=en_US:en
|
||||
|
||||
# Global LSIO modifications
|
||||
COPY ha_lsio.sh /ha_lsio.sh
|
||||
ARG CONFIGLOCATION="/config"
|
||||
RUN chmod 744 /ha_lsio.sh && if grep -qr "lsio" /etc; then /ha_lsio.sh "$CONFIGLOCATION"; fi && rm /ha_lsio.sh
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
# hadolint ignore=DL3015,SC2016
|
||||
# Add rootfs
|
||||
COPY rootfs/ /
|
||||
RUN find . -type f \( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \;
|
||||
RUN \
|
||||
# Install dependencies
|
||||
echo "Install dependencies" && \
|
||||
apt-get update -y && apt-get install curl gcc python3-dev git jq sudo php-mbstring procps -y && \
|
||||
\
|
||||
# Correct for systemctl
|
||||
mkdir -p /helpers && \
|
||||
curl -f -L -s -S https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/systemctl3.py -o /helpers/systemctl && \
|
||||
cp -rf /helpers/systemctl /bin/systemctl && \
|
||||
chmod a+x /bin/systemctl && \
|
||||
\
|
||||
# Correct for journalctl
|
||||
curl -f -L -s -S https://raw.githubusercontent.com/gdraheim/docker-systemctl-replacement/master/files/docker/journalctl3.py -o /helpers/journalctl && \
|
||||
cp -rf /helpers/journalctl /bin/journalctl && \
|
||||
chmod a+x /bin/journalctl && \
|
||||
\
|
||||
# Change user to pi and create /home/pi
|
||||
echo "setting users" && \
|
||||
if id abc >/dev/null 2>&1; then groupmod -o -g 101 abc && usermod -o -u 101 abc; fi && \
|
||||
groupadd --non-unique -g 1000 "$USER" && \
|
||||
useradd --non-unique --uid 1000 --gid 1000 -m "$USER" && \
|
||||
\
|
||||
# Ensure permissions
|
||||
echo "setting permissions" && \
|
||||
echo "$USER ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
|
||||
mkdir -p /home/"$USER"/.config/matplotlib && \
|
||||
chown -R "$USER":"$USER" /home/"$USER" && \
|
||||
git config --global --add safe.directory '*' && \
|
||||
\
|
||||
# Download installer
|
||||
curl -f -L -s -S "https://raw.githubusercontent.com/zach7036/BirdNET-Pi-Enhanced-Version/main/newinstaller.sh" -o /newinstaller.sh && \
|
||||
chmod 777 /newinstaller.sh && \
|
||||
\
|
||||
# Use installer to modify other scripts
|
||||
#######################################
|
||||
# Define file
|
||||
sed -i "1a /./newinstallermod.sh" /newinstaller.sh && \
|
||||
echo '#!/bin/bash' >> /newinstallermod.sh && \
|
||||
# Remove all instances of sudo from all other scripts
|
||||
echo 'for file in $(grep -srl "sudo" $HOME/BirdNET-Pi/scripts); do sed -i "s|sudo ||" "$file"; done' >> /newinstallermod.sh && \
|
||||
echo 'for file in $(grep -srl "my_dir" $HOME/BirdNET-Pi/scripts); do sed -i "s|\$my_dir|/config|" "$file"; done' >> /newinstallermod.sh && \
|
||||
# Set permission
|
||||
chmod +x /newinstallermod.sh && \
|
||||
\
|
||||
# Modify installer
|
||||
##################
|
||||
# zach7036's newinstaller.sh already clones zach7036/BirdNET-Pi-Enhanced-Version, so no repo rename is needed
|
||||
# Avoid rebooting at end of installation
|
||||
sed -i "/reboot/d" /newinstaller.sh && \
|
||||
# Use apt-get as without user action
|
||||
sed -i "s|apt |apt-get |g" /newinstaller.sh && \
|
||||
# Ensure chmod
|
||||
sed -i "/git clone/a chown -R 1000:1000 $HOME" /newinstaller.sh && \
|
||||
sed -i "/git clone/a chmod 777 \$HOME/BirdNET-Pi/scripts/*.sh" /newinstaller.sh && \
|
||||
# Disable datetimectl
|
||||
sed -i '/git clone/a sed -i "/CURRENT_TIMEZONE/s/$/ || true/" $HOME/BirdNET-Pi/scripts/install_birdnet.sh' /newinstaller.sh && \
|
||||
# Remove all instances of sudo from the newinstaller
|
||||
sed -i -e "s|== 0|== 7|g" -e "s|sudo -n true|true|g" -e "s|sudo -K|true|g" /newinstaller.sh && \
|
||||
\
|
||||
# Execute installer
|
||||
/./newinstaller.sh && \
|
||||
\
|
||||
# Install dateparser and resampy, upgrade numpy
|
||||
$PYTHON_VIRTUAL_ENV /usr/bin/pip3 install dateparser resampy && \
|
||||
\
|
||||
# Adapt for lsio usage of /app
|
||||
if [ -d /app ]; then rm -r /app; fi && \
|
||||
ln -s /home/"$USER" /app && \
|
||||
chown -R "$USER":"$USER" /home/"$USER" /app && \
|
||||
\
|
||||
# Give access to caddy for files owned by the user, to allow files modification
|
||||
groupmod -o -g 1000 caddy && usermod -o -u 1000 caddy && \
|
||||
\
|
||||
# Give access to audio devices
|
||||
usermod -aG audio "$USER" && \
|
||||
\
|
||||
# Ensure always pi is used
|
||||
grep -srl "/etc/passwd" "$HOME/BirdNET-Pi/" | while IFS= read -r file; do sed -i "s=/etc/passwd=/etc/passwd | head -1=g" "$file"; done && \
|
||||
\
|
||||
# Cleanup
|
||||
apt-get clean all && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
##################
|
||||
# 2 Modify Image #
|
||||
##################
|
||||
|
||||
# Set S6 wait time
|
||||
ENV S6_CMD_WAIT_FOR_SERVICES=1 \
|
||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
||||
S6_SERVICES_GRACETIME=0
|
||||
|
||||
##################
|
||||
# 3 Install apps #
|
||||
##################
|
||||
|
||||
# Uses /bin for compatibility purposes
|
||||
# hadolint ignore=DL4005
|
||||
RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; fi && \
|
||||
if [ ! -f /bin/bash ] && [ -f /usr/bin/bash ]; then ln -s /usr/bin/bash /bin/bash; fi
|
||||
|
||||
# Modules
|
||||
ARG MODULES="00-local_mounts.sh 00-smb_mounts.sh"
|
||||
|
||||
# Automatic modules download
|
||||
COPY ha_automodules.sh /ha_automodules.sh
|
||||
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
|
||||
|
||||
# Manual apps
|
||||
ENV PACKAGES="alsa-utils libasound2-plugins mosquitto-clients file pulseaudio"
|
||||
|
||||
# Automatic apps & bashio
|
||||
COPY ha_autoapps.sh /ha_autoapps.sh
|
||||
RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.sh
|
||||
|
||||
################
|
||||
# 4 Entrypoint #
|
||||
################
|
||||
|
||||
# Add entrypoint
|
||||
ENV S6_STAGE2_HOOK=/ha_entrypoint.sh
|
||||
COPY ha_entrypoint.sh /ha_entrypoint.sh
|
||||
RUN chmod 777 /ha_entrypoint.sh
|
||||
|
||||
# Install bashio
|
||||
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
|
||||
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
|
||||
|
||||
# Avoid config.yaml interference
|
||||
WORKDIR /config
|
||||
#ENTRYPOINT ["/lib/systemd/systemd"]
|
||||
#ENTRYPOINT [ "/usr/bin/env" ]
|
||||
#CMD [ "/ha_entrypoint.sh" ]
|
||||
#SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Allow a dockerfile independent from HA
|
||||
EXPOSE 80
|
||||
RUN mkdir -p /data /config
|
||||
|
||||
############
|
||||
# 5 Labels #
|
||||
############
|
||||
|
||||
ARG BUILD_ARCH
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_DESCRIPTION
|
||||
ARG BUILD_NAME
|
||||
ARG BUILD_REF
|
||||
ARG BUILD_REPOSITORY
|
||||
ARG BUILD_VERSION
|
||||
ENV BUILD_VERSION="${BUILD_VERSION}"
|
||||
LABEL \
|
||||
io.hass.name="${BUILD_NAME}" \
|
||||
io.hass.description="${BUILD_DESCRIPTION}" \
|
||||
io.hass.arch="${BUILD_ARCH}" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version=${BUILD_VERSION} \
|
||||
maintainer="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.title="${BUILD_NAME}" \
|
||||
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
|
||||
org.opencontainers.image.vendor="Home Assistant Add-ons" \
|
||||
org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.url="https://github.com/alexbelgium" \
|
||||
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
|
||||
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/main/README.md" \
|
||||
org.opencontainers.image.created=${BUILD_DATE} \
|
||||
org.opencontainers.image.revision=${BUILD_REF} \
|
||||
org.opencontainers.image.version=${BUILD_VERSION}
|
||||
|
||||
#################
|
||||
# 6 Healthcheck #
|
||||
#################
|
||||
|
||||
# Avoid spamming logs
|
||||
# hadolint ignore=SC2016
|
||||
RUN \
|
||||
# Handle Apache configuration
|
||||
if [ -d /etc/apache2/sites-available ]; then \
|
||||
for file in /etc/apache2/sites-*/*.conf; do \
|
||||
sed -i '/<VirtualHost/a \ \n # Match requests with the custom User-Agent "HealthCheck" \n SetEnvIf User-Agent "HealthCheck" dontlog \n # Exclude matching requests from access logs \n CustomLog ${APACHE_LOG_DIR}/access.log combined env=!dontlog' "$file"; \
|
||||
done; \
|
||||
fi && \
|
||||
\
|
||||
# Handle Nginx configuration
|
||||
if [ -f /etc/nginx/nginx.conf ]; then \
|
||||
awk '/http \{/{print; print "map $http_user_agent $dontlog {\n default 0;\n \"~*HealthCheck\" 1;\n}\naccess_log /var/log/nginx/access.log combined if=$dontlog;"; next}1' /etc/nginx/nginx.conf > /etc/nginx/nginx.conf.new && \
|
||||
mv /etc/nginx/nginx.conf.new /etc/nginx/nginx.conf; \
|
||||
fi
|
||||
|
||||
ENV HEALTH_PORT="80" \
|
||||
HEALTH_URL=""
|
||||
HEALTHCHECK \
|
||||
--interval=5s \
|
||||
--retries=5 \
|
||||
--start-period=30s \
|
||||
--timeout=25s \
|
||||
CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" &>/dev/null || exit 1
|
||||
187
birdnet-pi-zach/README.md
Normal file
187
birdnet-pi-zach/README.md
Normal file
@@ -0,0 +1,187 @@
|
||||
## ⚠ Open Issue : [[BirdNET-Pi Docker Standalone] Services wont start (opened 2025-06-24)](https://github.com/alexbelgium/hassio-addons/issues/1927) by [@sirtakahe](https://github.com/sirtakahe)
|
||||
|
||||
# Home assistant add-on: BirdNET-Pi (zach7036)
|
||||
|
||||
|
||||
I maintain this and other Home Assistant add-ons in my free time: keeping up with upstream changes, HA changes, and testing on real hardware takes a lot of time (and some money). I use around 5-10 of my >110 addons so regularly I install test machines (and purchase some test services such as vpn) that I don't use myself to troubleshoot and improve the addons
|
||||
|
||||
If this add-on saves you time or makes your setup easier, I would be very grateful for your support!
|
||||
|
||||
[![Buy me a coffee][donation-badge]](https://www.buymeacoffee.com/alexbelgium)
|
||||
[![Donate via PayPal][paypal-badge]](https://www.paypal.com/donate/?hosted_button_id=DZFULJZTP3UQA)
|
||||
|
||||
## Addon informations
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
[](https://www.codacy.com/gh/alexbelgium/hassio-addons/dashboard?utm_source=github.com&utm_medium=referral&utm_content=alexbelgium/hassio-addons&utm_campaign=Badge_Grade)
|
||||
[](https://github.com/alexbelgium/hassio-addons/actions/workflows/weekly-supelinter.yaml)
|
||||
[](https://github.com/alexbelgium/hassio-addons/actions/workflows/onpush_builder.yaml)
|
||||
|
||||
[donation-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee-%23d32f2f?logo=buy-me-a-coffee&style=flat&logoColor=white
|
||||
[paypal-badge]: https://img.shields.io/badge/Donate%20via%20PayPal-0070BA?logo=paypal&style=flat&logoColor=white
|
||||
|
||||
_Thanks to everyone having starred my repo! To star it click on the image below, then it will be on top right. Thanks!_
|
||||
|
||||
[](https://github.com/alexbelgium/hassio-addons/stargazers)
|
||||
|
||||

|
||||
|
||||
## About
|
||||
|
||||
_Note : For usage without HomeAssistant (classic docker container), see [here](https://github.com/alexbelgium/hassio-addons/blob/master/birdnet-pi-zach/README_standalone.md)_
|
||||
|
||||
---
|
||||
|
||||
[BirdNET-Pi](https://github.com/zach7036/BirdNET-Pi-Enhanced-Version) is an AI solution for continuous avian monitoring and identification originally developed by @mcguirepr89 on github (https://github.com/mcguirepr89/BirdNET-Pi). This add-on builds on the [BirdNET-Pi-Enhanced-Version fork by @zach7036](https://github.com/zach7036/BirdNET-Pi-Enhanced-Version), which adds new features and an updated UI. For the add-on based on @Nachtzuster's fork, use the separate `birdnet-pi` add-on instead.
|
||||
|
||||
Features of the addon :
|
||||
- Robust base image provided by [linuxserver](https://github.com/linuxserver/docker-baseimage-debian)
|
||||
- Working docker system thanks to https://github.com/gdraheim/docker-systemctl-replacement
|
||||
- Uses HA pulseaudio server
|
||||
- Uses HA tmpfs to store temporary files in ram and avoid disk wear
|
||||
- Exposes all config files to /config to allow remanence and easy access
|
||||
- Allows to modify the location of the stored bird songs (preferably to an external hdd)
|
||||
- Supports ingress, to allow secure remote access without exposing ports
|
||||
|
||||
## Configuration
|
||||
|
||||
---
|
||||
|
||||
Install, then start the addon a first time
|
||||
Webui can be found by two ways :
|
||||
- Ingress from HA (no password but some functions don't work)
|
||||
- Direct access with <http://homeassistant:port>, port being the one defined in the birdnet.conf. The username when asked for a password is `birdnet`, the password is the one that you can define in the birdnet.con (blank by default). This is different than the password from the addon options, which is the one that must be used to access the web terminal
|
||||
|
||||
Web terminal access : username `pi`, password : as defined in the addon options
|
||||
|
||||
You'll need a microphone : either use one connected to HA or the audio stream of a rstp camera.
|
||||
|
||||
Options can be configured through three ways :
|
||||
|
||||
- Addon options
|
||||
|
||||
```yaml
|
||||
BIRDSONGS_FOLDER: folder to store birdsongs file # It should be an ssd if you want to avoid clogging of analysis
|
||||
MQTT_DISABLED : if true, disables automatic mqtt publishing. Only valid if there is a local broker already available
|
||||
LIVESTREAM_BOOT_ENABLED: start livestream from boot, or from settings
|
||||
Use_tphakala_model_v2: false # switch to BirdNET-Go classifier files
|
||||
PROCESSED_FOLDER_ENABLED : if enabled, you need to set in the birdnet.conf (or the setting of birdnet) the number of last wav files that will be saved in the temporary folder "/tmp/Processed" within the tmpfs (so no disk wear) in case you want to retrieve them. This amount can be adapted from the addon options
|
||||
TZ: Etc/UTC specify a timezone to use, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
|
||||
pi_password: set the user password to access the web terminal
|
||||
localdisks: sda1 #put the hardware name of your drive to mount separated by commas, or its label. ex. sda1, sdb1, MYNAS...
|
||||
networkdisks: "//SERVER/SHARE" # optional, list of smb servers to mount, separated by commas
|
||||
cifsusername: "username" # optional, smb username, same for all smb shares
|
||||
cifspassword: "password" # optional, smb password
|
||||
cifsdomain: "domain" # optional, allow setting the domain for the smb share
|
||||
```
|
||||
|
||||
- Config.yaml
|
||||
Additional variables can be configured using the config.yaml file found in /config/db21ed7f_birdnet-pi-zach/config.yaml using the Filebrowser addon
|
||||
|
||||
- Config_env.yaml
|
||||
Additional environment variables can be configured there
|
||||
|
||||
### Mounting Drives
|
||||
|
||||
This addon supports mounting both local drives and remote SMB shares:
|
||||
|
||||
- **Local drives**: See [Mounting Local Drives in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-Local-Drives-in-Addons)
|
||||
- **Remote shares**: See [Mounting Remote Shares in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Mounting-remote-shares-in-Addons)
|
||||
|
||||
### Custom Scripts and Environment Variables
|
||||
|
||||
This addon supports custom scripts and environment variables through the `addon_config` mapping:
|
||||
|
||||
- **Custom scripts**: See [Running Custom Scripts in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Running-custom-scripts-in-Addons)
|
||||
- **env_vars option**: Use the add-on `env_vars` option to pass extra environment variables (uppercase or lowercase names). See https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2 for details.
|
||||
|
||||
## Installation
|
||||
|
||||
---
|
||||
|
||||
The installation of this add-on is pretty straightforward and not different in comparison to installing any other add-on.
|
||||
|
||||
1. Add my add-ons repository to your home assistant instance (in supervisor addons store at top right, or click button below if you have configured my HA)
|
||||
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
|
||||
1. Install this add-on.
|
||||
1. Click the `Save` button to store your configuration.
|
||||
1. Set the add-on options to your preferences
|
||||
1. Start the add-on.
|
||||
1. Check the logs of the add-on to see if everything went well.
|
||||
1. Open the webUI and adapt the software options
|
||||
|
||||
## Integration with HA
|
||||
|
||||
---
|
||||
### Apprise
|
||||
|
||||
You can use apprise to send notifications with mqtt, then act on those using HomeAssistant
|
||||
Further informations : https://wander.ingstar.com/projects/birdnetpi.html
|
||||
|
||||
### Automatic mqtt
|
||||
|
||||
If mqtt is installed, the addon automatically updates the birdnet topic with each detected species
|
||||
|
||||
## Using ssl
|
||||
|
||||
---
|
||||
|
||||
Option 1 : Install let's encrypt addon, generate certificates. They are by default certfile.pem and keyfile.pem stored in /ssl. Just enable ssl from the addon option and it will work.
|
||||
|
||||
Option 2 : enable port 80, define your BirdNET-Pi URL as https. Certificate will be automatically generated by caddy
|
||||
|
||||
## Improve detections
|
||||
|
||||
---
|
||||
|
||||
### Gain for card
|
||||
|
||||
Using alsamixer in the Terminal tab, make sure that the sound level is high enough but not too high (not in the red part)
|
||||
https://github.com/mcguirepr89/BirdNET-Pi/wiki/Adjusting-your-sound-card
|
||||
|
||||
### Ferrite
|
||||
|
||||
Adding ferrite beads lead in my case to worst noise
|
||||
|
||||
### Aux to usb adapters
|
||||
|
||||
Based on my test, only adapters using KT0210 (such as Ugreen's) work. I couldn't get adapters based on ALC to be detected.
|
||||
|
||||
### Microphone comparison
|
||||
|
||||
Recommended microphones ([full discussion here](https://github.com/mcguirepr89/BirdNET-Pi/discussions/39)):
|
||||
- Clippy EM272 (https://www.veldshop.nl/en/smart-clippy-em272z1-mono-omni-microphone.html) + ugreen aux to usb connector : best sensitivity with lavalier tech
|
||||
- Boya By-LM40 : best quality/price
|
||||
- Hyperx Quadcast : best sensitivity with cardioid tech
|
||||
|
||||
Conclusion, using mic from Dahua is good enough, EM272 is optimal, but Boya by-lm40 is a very good compromise as birndet model analysis the 0-15000Hz range
|
||||
|
||||

|
||||
|
||||
### Denoise ([Full discussion here](https://github.com/mcguirepr89/BirdNET-Pi/discussions/597))
|
||||
|
||||
Denoise is frowned upon by serious researchers. However it does seem to significantly increase quality of detection ! Here is how to do it in HA :
|
||||
- Using Portainer addon, go in the hassio_audio container, and modify the file /etc/pulse/system.pa to add the line `load-module module-echo-cancel`
|
||||
- Go in the Terminal addon, and type `ha audio restart`
|
||||
- Select the echo cancelled device as input device in the addon options
|
||||
|
||||
### High pass
|
||||
|
||||
Should be avoided as the model uses the whole 0-15khz range
|
||||
|
||||
## Common issues
|
||||
|
||||
Not yet available
|
||||
|
||||
## Support
|
||||
|
||||
Create an issue on github
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
|
||||
139
birdnet-pi-zach/README_standalone.md
Normal file
139
birdnet-pi-zach/README_standalone.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# BirdNET-Pi Docker Installation Guide
|
||||
|
||||
|
||||
I maintain this and other Home Assistant add-ons in my free time: keeping up with upstream changes, HA changes, and testing on real hardware takes a lot of time (and some money). I use around 5-10 of my >110 addons so regularly I install test machines (and purchase some test services such as vpn) that I don't use myself to troubleshoot and improve the addons
|
||||
|
||||
If this add-on saves you time or makes your setup easier, I would be very grateful for your support!
|
||||
|
||||
[![Buy me a coffee][donation-badge]](https://www.buymeacoffee.com/alexbelgium)
|
||||
[![Donate via PayPal][paypal-badge]](https://www.paypal.com/donate/?hosted_button_id=DZFULJZTP3UQA)
|
||||
|
||||
## Addon informations
|
||||
|
||||
[donation-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee-%23d32f2f?logo=buy-me-a-coffee&style=flat&logoColor=white
|
||||
[paypal-badge]: https://img.shields.io/badge/Donate%20via%20PayPal-0070BA?logo=paypal&style=flat&logoColor=white
|
||||
|
||||
This guide provides instructions on how to install and run the BirdNET-Pi container using Docker Compose without dependency on HomeAssistant.
|
||||
|
||||
_Note : For usage as an HomeAssistant addon, see [here](https://github.com/alexbelgium/hassio-addons/blob/master/birdnet-pi-zach/README.md)_
|
||||
|
||||
Thanks to @gotschi for the initial Docker Compose
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Ensure you have the following installed on your system:
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Create a directory for BirdNET-Pi**
|
||||
```sh
|
||||
mkdir -p ~/birdnet-pi-zach && cd ~/birdnet-pi-zach
|
||||
```
|
||||
|
||||
2. **Create a `docker-compose.yml` file**
|
||||
Create and open the file with:
|
||||
```sh
|
||||
nano docker-compose.yml
|
||||
```
|
||||
|
||||
Copy and paste the following configuration:
|
||||
```yaml
|
||||
services:
|
||||
birdnet-pi-zach:
|
||||
container_name: birdnet-pi-zach
|
||||
image: ghcr.io/alexbelgium/birdnet-pi-zach-amd64:latest # or ghcr.io/alexbelgium/birdnet-pi-zach-aarch64:latest depending on your system
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8001:8081" # Used to access WebUI
|
||||
- "80:80" # Optional: set to 80 to use Caddy's automatic SSL. Can otherwise be set to null to avoid opening an additional port
|
||||
environment:
|
||||
- TZ=Europe/Vienna # Optional: Set your timezone according to https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
- BIRDSONGS_FOLDER=/config/BirdSongs # Folder to store bird songs, be sure to use a path that is mapped to a volume (such as /config)
|
||||
- LIVESTREAM_BOOT_ENABLED=false # Enable/disable livestream on boot
|
||||
- Use_tphakala_model_v2=false # Switch to BirdNET-Go classifier files
|
||||
- ssl=false # Enable/disable SSL
|
||||
- certfile=fullchain.pem # SSL certificate file (located in /ssl/)
|
||||
- keyfile=privkey.pem # SSL key file (located in /ssl/)
|
||||
- pi_password= # Optional: Set web terminal password for user `pi`
|
||||
- MQTT_HOST_manual= # Optional: Manual MQTT host
|
||||
- MQTT_PASSWORD_manual= # Optional: Manual MQTT password
|
||||
- MQTT_PORT_manual= # Optional: Manual MQTT port
|
||||
- MQTT_USER_manual= # Optional: Manual MQTT user
|
||||
- PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||
- PULSE_COOKIE=/tmp/pulseaudio.cookie
|
||||
|
||||
volumes:
|
||||
- ./config:/config # All your configuration files - and location of the default Birdsongs folder
|
||||
- ./ssl:/ssl # SSL certificates
|
||||
- /dev/shm:/dev/shm # Shared memory
|
||||
- /tmp/pulseaudio.socket:/tmp/pulseaudio.socket
|
||||
- /tmp/pulseaudio.client.conf:/etc/pulse/client.conf
|
||||
|
||||
devices:
|
||||
- /dev/snd:/dev/snd
|
||||
|
||||
group_add:
|
||||
- audio
|
||||
|
||||
tmpfs:
|
||||
- /tmp # Optional
|
||||
|
||||
```
|
||||
|
||||
3. **Start the Container**
|
||||
Run the following command in the same directory as `docker-compose.yml`:
|
||||
```sh
|
||||
docker compose up -d
|
||||
```
|
||||
This will start the BirdNET-Pi container in detached mode.
|
||||
|
||||
4. **Access BirdNET-Pi Web UI**
|
||||
Open your browser and navigate to:
|
||||
```sh
|
||||
http://localhost:8001 # Or whatever port you have configured
|
||||
```
|
||||
Replace `localhost` with your server's IP address if running on another machine.
|
||||
When prompted for credentials in the web terminal, use the username `pi` and the password defined by `pi_password` (blank if unset).
|
||||
|
||||
## troubleshoot
|
||||
|
||||
If rtsp feed doesn't work, perhaps you need to add "-rtsp-transport tcp" to your ffmpeg instruction, or allow udp on your network
|
||||
|
||||
## Updating to the Latest Version
|
||||
|
||||
To check for new versions of the container and update:
|
||||
|
||||
1. **Check for the latest version**
|
||||
Visit the container registry:
|
||||
[https://github.com/alexbelgium/hassio-addons/pkgs/container/birdnet-pi-zach-amd64](https://github.com/alexbelgium/hassio-addons/pkgs/container/birdnet-pi-zach-amd64)
|
||||
|
||||
The latest version tag (e.g., `2025.02.23`) will be listed.
|
||||
|
||||
2. **Update and restart the container**
|
||||
Run the following commands:
|
||||
```sh
|
||||
docker compose pull birdnet-pi-zach
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
This pulls the latest image and restarts the container.
|
||||
|
||||
3. **Verify the update**
|
||||
```sh
|
||||
docker images | grep birdnet-pi-zach
|
||||
```
|
||||
This will show the latest downloaded image version.
|
||||
|
||||
## Stopping and Removing the Container
|
||||
|
||||
To stop and remove the container, run:
|
||||
```sh
|
||||
docker compose down
|
||||
```
|
||||
|
||||
This will stop and remove BirdNET-Pi while keeping the configuration and recorded songs intact.
|
||||
|
||||
---
|
||||
|
||||
Now you're all set to enjoy BirdNET-Pi with Docker! 🐦
|
||||
67
birdnet-pi-zach/apparmor.txt
Normal file
67
birdnet-pi-zach/apparmor.txt
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <tunables/global>
|
||||
|
||||
profile birdnet-pi-zach_addon flags=(attach_disconnected,mediate_deleted) {
|
||||
#include <abstractions/base>
|
||||
|
||||
capability chown,
|
||||
capability dac_override,
|
||||
capability dac_read_search,
|
||||
capability fowner,
|
||||
capability setgid,
|
||||
capability setuid,
|
||||
capability sys_chroot,
|
||||
capability sys_admin,
|
||||
file,
|
||||
signal,
|
||||
mount,
|
||||
umount,
|
||||
remount,
|
||||
network udp,
|
||||
network tcp,
|
||||
network dgram,
|
||||
network stream,
|
||||
network inet,
|
||||
network inet6,
|
||||
network netlink raw,
|
||||
network unix dgram,
|
||||
|
||||
|
||||
# S6-Overlay
|
||||
/init ix,
|
||||
/run/{s6,s6-rc*,service}/** ix,
|
||||
/package/** ix,
|
||||
/command/** ix,
|
||||
/run/{,**} rwk,
|
||||
/dev/tty rw,
|
||||
/bin/** ix,
|
||||
/usr/bin/** ix,
|
||||
/usr/lib/bashio/** ix,
|
||||
/etc/s6/** rix,
|
||||
/run/s6/** rix,
|
||||
/etc/services.d/** rwix,
|
||||
/etc/cont-init.d/** rwix,
|
||||
/etc/cont-finish.d/** rwix,
|
||||
/init rix,
|
||||
/var/run/** mrwkl,
|
||||
/var/run/ mrwkl,
|
||||
/dev/i2c-1 mrwkl,
|
||||
# Files required
|
||||
/dev/fuse mrwkl,
|
||||
/dev/sda1 mrwkl,
|
||||
/dev/sdb1 mrwkl,
|
||||
/dev/nvme0 mrwkl,
|
||||
/dev/nvme1 mrwkl,
|
||||
/dev/mmcblk0p1 mrwkl,
|
||||
/dev/* mrwkl,
|
||||
/tmp/** mrkwl,
|
||||
|
||||
# Data access
|
||||
/data/** rw,
|
||||
|
||||
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
|
||||
ptrace (trace,read) peer=docker-default,
|
||||
|
||||
# docker daemon confinement requires explict allow rule for signal
|
||||
signal (receive) set=(kill,term) peer=/usr/bin/docker,
|
||||
|
||||
}
|
||||
4
birdnet-pi-zach/build.yaml
Normal file
4
birdnet-pi-zach/build.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
build_from:
|
||||
aarch64: ghcr.io/linuxserver/baseimage-debian:arm64v8-bookworm
|
||||
amd64: ghcr.io/linuxserver/baseimage-debian:amd64-bookworm
|
||||
120
birdnet-pi-zach/config.yaml
Normal file
120
birdnet-pi-zach/config.yaml
Normal file
@@ -0,0 +1,120 @@
|
||||
arch:
|
||||
- aarch64
|
||||
- amd64
|
||||
audio: true
|
||||
backup: cold
|
||||
description: Realtime acoustic bird classification system
|
||||
devices:
|
||||
- /dev/dri
|
||||
- /dev/dri/card0
|
||||
- /dev/dri/card1
|
||||
- /dev/dri/renderD128
|
||||
- /dev/vchiq
|
||||
- /dev/video10
|
||||
- /dev/video11
|
||||
- /dev/video12
|
||||
- /dev/video13
|
||||
- /dev/video14
|
||||
- /dev/video15
|
||||
- /dev/video16
|
||||
- /dev/ttyUSB0
|
||||
- /dev/sda
|
||||
- /dev/sdb
|
||||
- /dev/sdc
|
||||
- /dev/sdd
|
||||
- /dev/sde
|
||||
- /dev/sdf
|
||||
- /dev/sdg
|
||||
- /dev/nvme
|
||||
- /dev/nvme0
|
||||
- /dev/nvme0n1
|
||||
- /dev/nvme0n1p1
|
||||
- /dev/nvme0n1p2
|
||||
- /dev/mmcblk
|
||||
- /dev/fuse
|
||||
- /dev/sda1
|
||||
- /dev/sdb1
|
||||
- /dev/sdc1
|
||||
- /dev/sdd1
|
||||
- /dev/sde1
|
||||
- /dev/sdf1
|
||||
- /dev/sdg1
|
||||
- /dev/sda2
|
||||
- /dev/sdb2
|
||||
- /dev/sdc2
|
||||
- /dev/sdd2
|
||||
- /dev/sde2
|
||||
- /dev/sdf2
|
||||
- /dev/sdg2
|
||||
- /dev/sda3
|
||||
- /dev/sdb3
|
||||
- /dev/sda4
|
||||
- /dev/sdb4
|
||||
- /dev/sda5
|
||||
- /dev/sda6
|
||||
- /dev/sda7
|
||||
- /dev/sda8
|
||||
- /dev/nvme0
|
||||
- /dev/nvme1
|
||||
- /dev/nvme2
|
||||
image: ghcr.io/alexbelgium/birdnet-pi-zach-{arch}
|
||||
ingress: true
|
||||
init: false
|
||||
map:
|
||||
- addon_config:rw
|
||||
- media:rw
|
||||
- share:rw
|
||||
- ssl
|
||||
name: BirdNET-Pi (zach7036)
|
||||
options:
|
||||
env_vars: []
|
||||
BIRDSONGS_FOLDER: /config/BirdSongs
|
||||
LIVESTREAM_BOOT_ENABLED: false
|
||||
MQTT_DISABLED: true
|
||||
TZ: Europe/Paris
|
||||
Use_tphakala_model_v2: false
|
||||
certfile: fullchain.pem
|
||||
keyfile: privkey.pem
|
||||
ssl: false
|
||||
panel_admin: false
|
||||
panel_icon: mdi:bird
|
||||
ports:
|
||||
80/tcp: null
|
||||
8081/tcp: 8081
|
||||
ports_description:
|
||||
80/tcp: "Optional : set to 80 to use caddy's automatic ssl"
|
||||
8081/tcp: Web ui
|
||||
privileged:
|
||||
- SYS_ADMIN
|
||||
- DAC_READ_SEARCH
|
||||
schema:
|
||||
env_vars:
|
||||
- name: match(^[A-Za-z0-9_]+$)
|
||||
value: str?
|
||||
BIRDSONGS_FOLDER: str?
|
||||
LIVESTREAM_BOOT_ENABLED: bool
|
||||
MQTT_DISABLED: bool?
|
||||
MQTT_HOST_manual: str?
|
||||
MQTT_PASSWORD_manual: password?
|
||||
MQTT_PORT_manual: int?
|
||||
MQTT_USER_manual: str?
|
||||
TZ: str?
|
||||
Use_tphakala_model_v2: bool
|
||||
certfile: str
|
||||
cifsdomain: str?
|
||||
cifspassword: str?
|
||||
cifsusername: str?
|
||||
keyfile: str
|
||||
localdisks: str?
|
||||
networkdisks: str?
|
||||
pi_password: password
|
||||
ssl: bool
|
||||
services:
|
||||
- mqtt:want
|
||||
slug: birdnet-pi-zach
|
||||
tmpfs: true
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/birdnet-pi-zach
|
||||
usb: true
|
||||
version: 2026.07.10
|
||||
video: true
|
||||
BIN
birdnet-pi-zach/icon.png
Normal file
BIN
birdnet-pi-zach/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
birdnet-pi-zach/logo.png
Normal file
BIN
birdnet-pi-zach/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
15
birdnet-pi-zach/rootfs/custom-services.d/00-php_pfm.sh
Executable file
15
birdnet-pi-zach/rootfs/custom-services.d/00-php_pfm.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Correct /config permissions after startup
|
||||
chown pi:pi /config
|
||||
|
||||
# Waiting for dbus
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]]; do
|
||||
sleep 1s
|
||||
done
|
||||
|
||||
TZ_VALUE="$(timedatectl show -p Timezone --value)"
|
||||
export TZ="$TZ_VALUE"
|
||||
echo "Starting service: php pfm"
|
||||
exec /usr/sbin/php-fpm* -F
|
||||
13
birdnet-pi-zach/rootfs/custom-services.d/01-avahi.sh
Executable file
13
birdnet-pi-zach/rootfs/custom-services.d/01-avahi.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
# Waiting for dbus
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]]; do
|
||||
sleep 1s
|
||||
done
|
||||
|
||||
TZ_VALUE="$(timedatectl show -p Timezone --value)"
|
||||
export TZ="$TZ_VALUE"
|
||||
|
||||
echo "Starting service: avahi daemon"
|
||||
exec \
|
||||
avahi-daemon --no-chroot
|
||||
25
birdnet-pi-zach/rootfs/custom-services.d/02-caddy.sh
Executable file
25
birdnet-pi-zach/rootfs/custom-services.d/02-caddy.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Dependencies
|
||||
sockfile="empty"
|
||||
until [[ -e /var/run/dbus/system_bus_socket ]] && [[ -e "$sockfile" ]]; do
|
||||
sleep 1s
|
||||
sockfile="$(find /run/php -name "*.sock")"
|
||||
done
|
||||
|
||||
# Correct fpm.sock
|
||||
chown caddy:caddy /run/php/php*-fpm.sock
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" /helpers/caddy_ingress.sh
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|/run/php/php-fpm.sock|$sockfile|g" "$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh
|
||||
|
||||
# Set timezone
|
||||
TZ_VALUE="$(timedatectl show -p Timezone --value)"
|
||||
export TZ="$TZ_VALUE"
|
||||
|
||||
# Update caddyfile with password
|
||||
/."$HOME"/BirdNET-Pi/scripts/update_caddyfile.sh &> /dev/null || true
|
||||
|
||||
echo "Starting service: caddy"
|
||||
/usr/bin/caddy run --config /etc/caddy/Caddyfile
|
||||
10
birdnet-pi-zach/rootfs/custom-services.d/02-nginx.sh
Executable file
10
birdnet-pi-zach/rootfs/custom-services.d/02-nginx.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
# Set timezone
|
||||
TZ_VALUE="$(timedatectl show -p Timezone --value)"
|
||||
export TZ="$TZ_VALUE"
|
||||
|
||||
echo "Starting service: nginx"
|
||||
nginx
|
||||
282
birdnet-pi-zach/rootfs/custom-services.d/30-monitoring.sh
Executable file
282
birdnet-pi-zach/rootfs/custom-services.d/30-monitoring.sh
Executable file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck shell=bash
|
||||
# Improved BirdNET-Pi Monitoring Script with Recovery Alerts and Condensed Logs
|
||||
|
||||
HOME="/home/pi"
|
||||
|
||||
########################################
|
||||
# Logging Functions (color-coded for terminal clarity)
|
||||
########################################
|
||||
log_green() { echo -e "\033[32m$1\033[0m"; }
|
||||
log_red() { echo -e "\033[31m$1\033[0m"; }
|
||||
log_yellow() { echo -e "\033[33m$1\033[0m"; }
|
||||
log_blue() { echo -e "\033[34m$1\033[0m"; }
|
||||
|
||||
########################################
|
||||
# Read configuration
|
||||
########################################
|
||||
set +u
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/birdnet/birdnet.conf
|
||||
|
||||
########################################
|
||||
# Wait 5 minutes for system stabilization
|
||||
########################################
|
||||
sleep 5m
|
||||
|
||||
log_green "Starting service: throttlerecording"
|
||||
|
||||
########################################
|
||||
# Define Directories, Files, and Constants
|
||||
########################################
|
||||
INGEST_DIR="$(readlink -f "$HOME/BirdSongs/StreamData")"
|
||||
ANALYZING_NOW_FILE="$INGEST_DIR/analyzing_now.txt"
|
||||
touch "$ANALYZING_NOW_FILE"
|
||||
BIRDSONGS_DIR="$(readlink -f "$HOME/BirdSongs/Extracted/By_Date")"
|
||||
|
||||
# Ensure directories and set permissions
|
||||
mkdir -p "$INGEST_DIR" || {
|
||||
log_red "Failed to create directory: $INGEST_DIR"
|
||||
exit 1
|
||||
}
|
||||
chown -R pi:pi "$INGEST_DIR" || log_yellow "Could not change ownership for $INGEST_DIR"
|
||||
chmod -R 755 "$INGEST_DIR" || log_yellow "Could not set permissions for $INGEST_DIR"
|
||||
|
||||
# Services to monitor
|
||||
SERVICES=(birdnet_analysis chart_viewer spectrogram_viewer birdnet_recording birdnet_log birdnet_stats)
|
||||
|
||||
########################################
|
||||
# Notification settings
|
||||
########################################
|
||||
NOTIFICATION_INTERVAL=1800 # 30 minutes in seconds
|
||||
NOTIFICATION_INTERVAL_IN_MINUTES=$((NOTIFICATION_INTERVAL / 60))
|
||||
last_notification_time=0
|
||||
issue_reported=0 # 1 = an issue was reported, 0 = system is normal
|
||||
declare -A SERVICE_INACTIVE_COUNT=()
|
||||
|
||||
# Disk usage threshold (percentage)
|
||||
DISK_USAGE_THRESHOLD=95
|
||||
|
||||
# "Analyzing" file check variables
|
||||
same_file_counter=0
|
||||
SAME_FILE_THRESHOLD=2
|
||||
if [[ -f "$ANALYZING_NOW_FILE" ]]; then
|
||||
analyzing_now=$(< "$ANALYZING_NOW_FILE")
|
||||
else
|
||||
analyzing_now=""
|
||||
fi
|
||||
|
||||
########################################
|
||||
# Notification Functions
|
||||
########################################
|
||||
apprisealert() {
|
||||
local issue_message="$1"
|
||||
local current_time
|
||||
current_time=$(date +%s)
|
||||
|
||||
# Calculate time_diff in minutes since last notification
|
||||
local time_diff=$(((current_time - last_notification_time) / 60))
|
||||
|
||||
# Throttle notifications
|
||||
if ((time_diff < NOTIFICATION_INTERVAL_IN_MINUTES)); then
|
||||
log_yellow "Notification suppressed (last sent ${time_diff} minutes ago)."
|
||||
return
|
||||
fi
|
||||
|
||||
local stopped_service="<br><b>Stopped services:</b> "
|
||||
for service in "${SERVICES[@]}"; do
|
||||
if [[ "$(systemctl is-active "$service")" != "active" ]]; then
|
||||
stopped_service+="$service; "
|
||||
fi
|
||||
done
|
||||
|
||||
local notification="<b>Issue:</b> $issue_message"
|
||||
notification+="$stopped_service"
|
||||
notification+="<br><b>System:</b> ${SITE_NAME:-$(hostname)}"
|
||||
notification+="<br>Available disk space: $(df -h "$BIRDSONGS_DIR" | awk 'NR==2 {print $4}')"
|
||||
notification+="<br>----Last log lines----"
|
||||
notification+="<br> $(timeout 15 cat /proc/1/fd/1 | head -n 5)"
|
||||
notification+="<br>----------------------"
|
||||
[[ -n "$BIRDNETPI_URL" ]] && notification+="<br><a href=\"$BIRDNETPI_URL\">Access your BirdNET-Pi</a>"
|
||||
|
||||
local TITLE="BirdNET-Analyzer Alert"
|
||||
if [[ -f "$HOME/BirdNET-Pi/birdnet/bin/apprise" && -s "$HOME/BirdNET-Pi/apprise.txt" ]]; then
|
||||
"$HOME/BirdNET-Pi/birdnet/bin/apprise" -vv -t "$TITLE" -b "$notification" \
|
||||
--input-format=html --config="$HOME/BirdNET-Pi/apprise.txt"
|
||||
last_notification_time=$current_time
|
||||
issue_reported=1
|
||||
else
|
||||
log_red "Apprise not configured or missing!"
|
||||
fi
|
||||
}
|
||||
|
||||
apprisealert_recovery() {
|
||||
# Only send a recovery message if we had previously reported an issue
|
||||
if ((issue_reported == 1)); then
|
||||
log_green "$(date) INFO: System is back to normal. Sending recovery notification."
|
||||
|
||||
local TITLE="BirdNET-Pi System Recovered"
|
||||
local notification="<b>All monitored services are back to normal.</b><br>"
|
||||
notification+="<b>System:</b> ${SITE_NAME:-$(hostname)}<br>"
|
||||
notification+="Available disk space: $(df -h "$BIRDSONGS_DIR" | awk 'NR==2 {print $4}')"
|
||||
|
||||
if [[ -f "$HOME/BirdNET-Pi/birdnet/bin/apprise" && -s "$HOME/BirdNET-Pi/apprise.txt" ]]; then
|
||||
"$HOME/BirdNET-Pi/birdnet/bin/apprise" -vv -t "$TITLE" -b "$notification" \
|
||||
--input-format=html --config="$HOME/BirdNET-Pi/apprise.txt"
|
||||
fi
|
||||
issue_reported=0
|
||||
fi
|
||||
}
|
||||
|
||||
########################################
|
||||
# Helper Checks
|
||||
########################################
|
||||
|
||||
check_disk_space() {
|
||||
local current_usage
|
||||
current_usage=$(df -h "$BIRDSONGS_DIR" | awk 'NR==2 {print $5}' | sed 's/%//')
|
||||
|
||||
if ((current_usage >= DISK_USAGE_THRESHOLD)); then
|
||||
log_red "$(date) INFO: Disk usage is at ${current_usage}% (CRITICAL!)"
|
||||
apprisealert "Disk usage critical: ${current_usage}%"
|
||||
return 1
|
||||
else
|
||||
log_green "$(date) INFO: Disk usage is within acceptable limits (${current_usage}%)."
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
check_analyzing_now() {
|
||||
local current_file
|
||||
current_file=$(cat "$ANALYZING_NOW_FILE" 2> /dev/null)
|
||||
if [[ "$current_file" == "$analyzing_now" ]]; then
|
||||
((same_file_counter++))
|
||||
else
|
||||
same_file_counter=0
|
||||
analyzing_now="$current_file"
|
||||
fi
|
||||
|
||||
if ((same_file_counter >= SAME_FILE_THRESHOLD)); then
|
||||
log_red "$(date) INFO: 'analyzing_now' file unchanged for $SAME_FILE_THRESHOLD iterations."
|
||||
apprisealert "No change in analyzing_now for ${SAME_FILE_THRESHOLD} iterations"
|
||||
"$HOME/BirdNET-Pi/scripts/restart_services.sh"
|
||||
same_file_counter=0
|
||||
return 1
|
||||
else
|
||||
# Only log if it changed this iteration
|
||||
if ((same_file_counter == 0)); then
|
||||
log_green "$(date) INFO: 'analyzing_now' file has been updated."
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
check_queue() {
|
||||
local wav_count
|
||||
wav_count=$(find -L "$INGEST_DIR" -maxdepth 1 -name '*.wav' | wc -l)
|
||||
|
||||
log_green "$(date) INFO: Queue is at a manageable level (${wav_count} wav files)."
|
||||
|
||||
if ((wav_count > 50)); then
|
||||
log_red "$(date) INFO: Queue >50. Stopping recorder + restarting analyzer."
|
||||
apprisealert "Queue exceeded 50: stopping recorder, restarting analyzer."
|
||||
sudo systemctl stop birdnet_recording
|
||||
sudo systemctl restart birdnet_analysis
|
||||
return 1
|
||||
elif ((wav_count > 30)); then
|
||||
log_red "$(date) INFO: Queue >30. Restarting analyzer."
|
||||
apprisealert "Queue exceeded 30: restarting analyzer."
|
||||
sudo systemctl restart birdnet_analysis
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_services() {
|
||||
local any_inactive=0
|
||||
|
||||
for service in "${SERVICES[@]}"; do
|
||||
if [[ "$(systemctl is-active "$service")" != "active" ]]; then
|
||||
SERVICE_INACTIVE_COUNT["$service"]=$((SERVICE_INACTIVE_COUNT["$service"] + 1))
|
||||
|
||||
if ((SERVICE_INACTIVE_COUNT["$service"] == 1)); then
|
||||
# First time inactive => Try to start
|
||||
log_yellow "$(date) INFO: Service '$service' is inactive. Attempting to start..."
|
||||
systemctl start "$service"
|
||||
any_inactive=1
|
||||
elif ((SERVICE_INACTIVE_COUNT["$service"] == 2)); then
|
||||
# Second consecutive time => Send an alert
|
||||
log_red "$(date) INFO: Service '$service' is still inactive after restart attempt."
|
||||
apprisealert "Service '$service' remains inactive after restart attempt."
|
||||
any_inactive=1
|
||||
else
|
||||
# Beyond second check => keep logging or do advanced actions
|
||||
log_red "$(date) INFO: Service '$service' inactive for ${SERVICE_INACTIVE_COUNT["$service"]} checks in a row."
|
||||
any_inactive=1
|
||||
fi
|
||||
else
|
||||
# Service is active => reset counter
|
||||
if ((SERVICE_INACTIVE_COUNT["$service"] > 0)); then
|
||||
log_green "$(date) INFO: Service '$service' is back to active. Resetting counter."
|
||||
fi
|
||||
SERVICE_INACTIVE_COUNT["$service"]=0
|
||||
fi
|
||||
done
|
||||
|
||||
if ((any_inactive == 0)); then
|
||||
log_green "$(date) INFO: All services are active"
|
||||
return 0
|
||||
else
|
||||
log_red "$(date) INFO: One or more services are inactive"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_for_empty_stream() {
|
||||
local log_tail
|
||||
log_tail=$(timeout 15 cat /proc/1/fd/1 | tail -n 5)
|
||||
|
||||
if echo "$log_tail" | grep -q "Haliastur indus"; then
|
||||
log_red "$(date) INFO: Potential empty stream detected (frequent 'Haliastur indus')."
|
||||
apprisealert "Potential empty stream detected — frequent 'Haliastur indus' in log"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
########################################
|
||||
# Main Monitoring Loop
|
||||
########################################
|
||||
TZ_VALUE="$(timedatectl show -p Timezone --value)"
|
||||
export TZ="$TZ_VALUE"
|
||||
|
||||
while true; do
|
||||
sleep 61
|
||||
log_blue "----------------------------------------"
|
||||
log_blue "$(date) INFO: Starting monitoring check"
|
||||
any_issue=0
|
||||
|
||||
# 1) Disk usage
|
||||
check_disk_space || any_issue=1
|
||||
|
||||
# 2) 'analyzing_now' file
|
||||
check_analyzing_now || any_issue=1
|
||||
|
||||
# 3) Queue check
|
||||
check_queue || any_issue=1
|
||||
|
||||
# 4) Services check
|
||||
check_services || any_issue=1
|
||||
|
||||
# 5) Check for potential empty stream
|
||||
check_for_empty_stream || any_issue=1
|
||||
|
||||
# Final summary
|
||||
if ((any_issue == 0)); then
|
||||
log_green "$(date) INFO: All systems are functioning normally"
|
||||
apprisealert_recovery
|
||||
else
|
||||
log_red "$(date) INFO: Issues detected. System status is not fully operational."
|
||||
fi
|
||||
log_blue "----------------------------------------"
|
||||
done
|
||||
51
birdnet-pi-zach/rootfs/etc/cont-finish.d/savestreamdata.sh
Executable file
51
birdnet-pi-zach/rootfs/etc/cont-finish.d/savestreamdata.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Maximum file size in bytes (50MB)
|
||||
MAX_SIZE=$((50 * 1024 * 1024))
|
||||
|
||||
# Function to check if a file is a valid WAV
|
||||
is_valid_wav() {
|
||||
local file="$1"
|
||||
# Check if the file contains a valid WAV header
|
||||
file "$file" | grep -qE 'WAVE audio'
|
||||
}
|
||||
|
||||
if [ -d "$HOME"/BirdSongs/StreamData ]; then
|
||||
bashio::log.fatal "Container stopping, saving temporary files."
|
||||
|
||||
# Stop the services in parallel
|
||||
if systemctl is-active --quiet birdnet_analysis; then
|
||||
bashio::log.info "Stopping birdnet_analysis service."
|
||||
systemctl stop birdnet_analysis &
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet birdnet_recording; then
|
||||
bashio::log.info "Stopping birdnet_recording service."
|
||||
systemctl stop birdnet_recording &
|
||||
fi
|
||||
|
||||
# Wait for both services to stop
|
||||
wait
|
||||
|
||||
# Create the destination directory
|
||||
mkdir -p /config/TemporaryFiles
|
||||
|
||||
# Move only valid WAV files under 50MB
|
||||
shopt -s nullglob # Prevent errors if no files match
|
||||
for file in "$HOME"/BirdSongs/StreamData/*.wav; do
|
||||
if [ -f "$file" ] && [ "$(stat --format="%s" "$file")" -lt "$MAX_SIZE" ] && is_valid_wav "$file"; then
|
||||
if mv -v "$file" /config/TemporaryFiles/; then
|
||||
bashio::log.info "Moved valid WAV file: $(basename "$file")"
|
||||
else
|
||||
bashio::log.error "Failed to move: $(basename "$file")"
|
||||
fi
|
||||
else
|
||||
bashio::log.warning "Skipping invalid or large file: $(basename "$file")"
|
||||
fi
|
||||
done
|
||||
|
||||
bashio::log.info "... files safe, allowing container to stop."
|
||||
else
|
||||
bashio::log.info "No StreamData directory to process."
|
||||
fi
|
||||
126
birdnet-pi-zach/rootfs/etc/cont-init.d/01-structure.sh
Executable file
126
birdnet-pi-zach/rootfs/etc/cont-init.d/01-structure.sh
Executable file
@@ -0,0 +1,126 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
###############
|
||||
# SET /CONFIG #
|
||||
###############
|
||||
|
||||
bashio::log.info "Ensuring the file structure is correct:"
|
||||
|
||||
# Create default configuration files if not present
|
||||
echo "... creating default files"
|
||||
DEFAULT_FILES=("body.txt" "apprise.txt" "exclude_species_list.txt" "IdentifiedSoFar.txt" "disk_check_exclude.txt" "confirmed_species_list.txt" "blacklisted_images.txt" "whitelist_species_list.txt")
|
||||
for file in "${DEFAULT_FILES[@]}"; do
|
||||
if [ ! -f "/config/$file" ]; then
|
||||
echo "" > "/config/$file"
|
||||
fi
|
||||
done
|
||||
touch /config/include_species_list.txt # Ensure this is always created
|
||||
|
||||
# Set BirdSongs folder location from configuration if specified
|
||||
BIRDSONGS_FOLDER="/config/BirdSongs"
|
||||
if bashio::config.has_value "BIRDSONGS_FOLDER"; then
|
||||
BIRDSONGS_FOLDER_OPTION="$(bashio::config "BIRDSONGS_FOLDER")"
|
||||
echo "... BIRDSONGS_FOLDER set to $BIRDSONGS_FOLDER_OPTION"
|
||||
mkdir -p "$BIRDSONGS_FOLDER_OPTION" || bashio::log.fatal "...... folder couldn't be created"
|
||||
if [ -d "$BIRDSONGS_FOLDER_OPTION" ]; then
|
||||
if [ "$(stat -c '%u:%g' "$BIRDSONGS_FOLDER_OPTION")" != "$(id -u pi):$(id -g pi)" ]; then
|
||||
chown -R pi:pi "$BIRDSONGS_FOLDER_OPTION" || bashio::log.fatal "...... folder couldn't be given permissions for $(id -u pi):$(id -g pi)"
|
||||
fi
|
||||
BIRDSONGS_FOLDER="$BIRDSONGS_FOLDER_OPTION"
|
||||
else
|
||||
bashio::log.warning "BIRDSONGS_FOLDER reverted to /config/BirdSongs"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create default folders
|
||||
echo "... creating default folders; it is highly recommended to store these on an SSD"
|
||||
mkdir -p "$BIRDSONGS_FOLDER/By_Date" "$BIRDSONGS_FOLDER/Charts"
|
||||
|
||||
# Use tmpfs if installed
|
||||
if df -T /tmp | grep -q "tmpfs"; then
|
||||
echo "... tmpfs detected, using it for StreamData and Processed to reduce disk wear"
|
||||
mkdir -p /tmp/StreamData /tmp/Processed
|
||||
[ -d "$HOME/BirdSongs/StreamData" ] && rm -r "$HOME/BirdSongs/StreamData"
|
||||
[ -d "$HOME/BirdSongs/Processed" ] && rm -r "$HOME/BirdSongs/Processed"
|
||||
sudo -u pi ln -fs /tmp/StreamData "$HOME/BirdSongs/StreamData"
|
||||
sudo -u pi ln -fs /tmp/Processed "$HOME/BirdSongs/Processed"
|
||||
fi
|
||||
|
||||
# Set permissions for created files and folders
|
||||
echo "... setting permissions for user pi"
|
||||
chown -R pi:pi /config /etc/birdnet "$BIRDSONGS_FOLDER" /tmp
|
||||
chmod -R 755 /config /etc/birdnet "$BIRDSONGS_FOLDER" /tmp
|
||||
|
||||
# Backup default birdnet.conf for sanity check
|
||||
cp "$HOME/BirdNET-Pi/birdnet.conf" "$HOME/BirdNET-Pi/birdnet.bak"
|
||||
|
||||
# Create default birdnet.conf if not existing
|
||||
if [ ! -f /config/birdnet.conf ]; then
|
||||
cp -f "$HOME/BirdNET-Pi/birdnet.conf" /config/
|
||||
fi
|
||||
|
||||
# Create default birds.db
|
||||
if [ ! -f /config/birds.db ]; then
|
||||
echo "... creating initial db"
|
||||
"$HOME/BirdNET-Pi/scripts/createdb.sh"
|
||||
cp "$HOME/BirdNET-Pi/scripts/birds.db" /config/
|
||||
elif [ "$(stat -c%s /config/birds.db)" -lt 10240 ]; then
|
||||
echo "... your db is corrupted, creating new one"
|
||||
rm /config/birds.db
|
||||
"$HOME/BirdNET-Pi/scripts/createdb.sh"
|
||||
cp "$HOME/BirdNET-Pi/scripts/birds.db" /config/
|
||||
fi
|
||||
|
||||
# Symlink configuration files
|
||||
echo "... creating symlinks for configuration files"
|
||||
CONFIG_FILES=("$HOME/BirdNET-Pi/body.txt" "$HOME/BirdNET-Pi/birdnet.conf" "$HOME/BirdNET-Pi/scripts/whitelist_species_list.txt" "$HOME/BirdNET-Pi/blacklisted_images.txt" "$HOME/BirdNET-Pi/scripts/birds.db" "$HOME/BirdNET-Pi/BirdDB.txt" "$HOME/BirdNET-Pi/scripts/disk_check_exclude.txt" "$HOME/BirdNET-Pi/apprise.txt" "$HOME/BirdNET-Pi/exclude_species_list.txt" "$HOME/BirdNET-Pi/include_species_list.txt" "$HOME/BirdNET-Pi/IdentifiedSoFar.txt" "$HOME/BirdNET-Pi/scripts/confirmed_species_list.txt")
|
||||
|
||||
for file in "${CONFIG_FILES[@]}"; do
|
||||
filename="${file##*/}"
|
||||
[ ! -f "/config/$filename" ] && touch "/config/$filename"
|
||||
[ -e "$file" ] && rm "$file"
|
||||
sudo -u pi ln -fs "/config/$filename" "$file"
|
||||
sudo -u pi ln -fs "/config/$filename" "$HOME/BirdNET-Pi/$filename"
|
||||
sudo -u pi ln -fs "/config/$filename" "$HOME/BirdNET-Pi/scripts/$filename"
|
||||
sudo -u pi ln -fs "/config/$filename" "/etc/birdnet/$filename"
|
||||
done
|
||||
|
||||
# Create thisrun.txt for legacy modes
|
||||
cp "$HOME"/BirdNET-Pi/birdnet.conf "$HOME"/BirdNET-Pi/scripts/thisrun.txt
|
||||
touch "$HOME"/BirdNET-Pi/scripts/lastrun.txt
|
||||
chown pi:pi "$HOME"/BirdNET-Pi/scripts/thisrun.txt
|
||||
chown pi:pi "$HOME"/BirdNET-Pi/scripts/lastrun.txt
|
||||
|
||||
# Symlink
|
||||
chown pi:pi /usr/local/bin/*
|
||||
|
||||
# Symlink BirdSongs folders
|
||||
for folder in By_Date Charts; do
|
||||
echo "... creating symlink for $BIRDSONGS_FOLDER/$folder"
|
||||
[ -d "$HOME/BirdSongs/Extracted/${folder:?}" ] && rm -r "$HOME/BirdSongs/Extracted/$folder"
|
||||
sudo -u pi ln -fs "$BIRDSONGS_FOLDER/$folder" "$HOME/BirdSongs/Extracted/$folder"
|
||||
done
|
||||
|
||||
# Set permissions for newly created files and folders
|
||||
echo "... checking and setting permissions"
|
||||
chmod -R 755 /config/*
|
||||
chmod 755 /config
|
||||
|
||||
# Create folder for matplotlib
|
||||
echo "... setting up Matplotlabdir"
|
||||
mkdir -p "$HOME"/.cache/matplotlib
|
||||
chown -R "pi:pi" "$HOME"/.cache/matplotlib
|
||||
chmod 755 "$HOME"/.cache/matplotlib
|
||||
50
birdnet-pi-zach/rootfs/etc/cont-init.d/02-restorestreamdata.sh
Executable file
50
birdnet-pi-zach/rootfs/etc/cont-init.d/02-restorestreamdata.sh
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
######################
|
||||
# RESTORE STREAMDATA #
|
||||
######################
|
||||
|
||||
if [ -d /config/TemporaryFiles ]; then
|
||||
|
||||
# Check if there are .wav files in /config/TemporaryFiles
|
||||
if find /config/TemporaryFiles -type f -name "*.wav" | grep -q .; then
|
||||
bashio::log.warning "Container was stopped while files were still being analyzed."
|
||||
echo "... restoring .wav files from /config/TemporaryFiles to $HOME/BirdSongs/StreamData."
|
||||
|
||||
# Create the destination directory if it does not exist
|
||||
mkdir -p "$HOME"/BirdSongs/StreamData
|
||||
|
||||
# Count the number of .wav files to be moved
|
||||
file_count=$(find /config/TemporaryFiles -type f -name "*.wav" | wc -l)
|
||||
echo "... found $file_count .wav files to restore."
|
||||
|
||||
# Move the .wav files using `mv` to avoid double log entries
|
||||
mv -v /config/TemporaryFiles/*.wav "$HOME"/BirdSongs/StreamData/
|
||||
|
||||
# Update permissions only if files were moved successfully
|
||||
if [ "$file_count" -gt 0 ]; then
|
||||
chown -R pi:pi "$HOME"/BirdSongs/StreamData
|
||||
fi
|
||||
|
||||
echo "... $file_count files restored successfully."
|
||||
else
|
||||
echo "... no .wav files found to restore."
|
||||
fi
|
||||
|
||||
# Clean up the source folder if it is empty
|
||||
rm -r /config/TemporaryFiles
|
||||
|
||||
fi
|
||||
75
birdnet-pi-zach/rootfs/etc/cont-init.d/31-config_checks.sh
Executable file
75
birdnet-pi-zach/rootfs/etc/cont-init.d/31-config_checks.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
######################
|
||||
# CHECK BIRDNET.CONF #
|
||||
######################
|
||||
|
||||
bashio::log.info "Checking your birdnet.conf file integrity"
|
||||
|
||||
# Set variables
|
||||
configcurrent="$HOME"/BirdNET-Pi/birdnet.conf
|
||||
configtemplate="$HOME"/BirdNET-Pi/birdnet.bak
|
||||
|
||||
# Ensure both files exist before proceeding
|
||||
if [ ! -f "$configcurrent" ] || [ ! -f "$configtemplate" ]; then
|
||||
bashio::log.fatal "Missing required birdnet.conf or birdnet.bak file. Please ensure both are present."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract variable names from config template and read each one
|
||||
grep -o '^[^#=]*=' "$configtemplate" | sed 's/=//' | while read -r var; do
|
||||
# Check if the variable is in configcurrent, if not, append it
|
||||
if ! grep -q "^$var=" "$configcurrent"; then
|
||||
bashio::log.warning "...$var was missing from your birdnet.conf file, it was re-added"
|
||||
grep "^$var=" "$configtemplate" >> "$configcurrent"
|
||||
fi
|
||||
# Check for duplicates
|
||||
if [ "$(grep -c "^$var=" "$configcurrent")" -gt 1 ]; then
|
||||
bashio::log.error "Duplicate variable $var found in $configcurrent, all were commented out except for the first one"
|
||||
sed -i "0,/^$var=/!s/^$var=/#$var=/" "$configcurrent"
|
||||
fi
|
||||
done
|
||||
|
||||
##############
|
||||
# CHECK PORT #
|
||||
##############
|
||||
|
||||
if [[ "$(bashio::addon.port "80")" == 3000 ]]; then
|
||||
bashio::log.fatal "This is crazy but your port is set to 3000 and streamlit doesn't accept this port! You need to change it from the addon options and restart. Thanks"
|
||||
sleep infinity
|
||||
fi
|
||||
|
||||
##################
|
||||
# PERFORM UPDATE #
|
||||
##################
|
||||
|
||||
bashio::log.info "Performing potential updates"
|
||||
|
||||
# Adapt update_birdnet_snippets
|
||||
sed -i "s|systemctl list-unit-files|false \&\& echo|g" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh # Avoid systemctl
|
||||
sed -i "/systemctl /d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh # Avoid systemctl
|
||||
sed -i "/install_tmp_mount/d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh # Use HA tmp
|
||||
sed -i "/find /d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh # Not useful
|
||||
sed -i "/set -x/d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh # Not useful
|
||||
sed -i "/restart_services/d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh # Not useful
|
||||
sed -i "s|/etc/birdnet/birdnet.conf|/config/birdnet.conf|g" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
|
||||
sed -i "/update_caddyfile/c echo \"yes\"" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh # Avoid systemctl
|
||||
|
||||
# Execute update_birdnet_snippets
|
||||
export RECS_DIR="$HOME/BirdSongs"
|
||||
export EXTRACTED="$HOME/BirdSongs/Extracted"
|
||||
chmod +x "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
|
||||
"$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
|
||||
84
birdnet-pi-zach/rootfs/etc/cont-init.d/33-mqtt.sh
Executable file
84
birdnet-pi-zach/rootfs/etc/cont-init.d/33-mqtt.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
############
|
||||
# SET MQTT #
|
||||
############
|
||||
|
||||
# Function to perform common setup steps
|
||||
common_steps() {
|
||||
# Attempt to connect to the MQTT broker
|
||||
TOPIC="birdnet"
|
||||
if mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" -t "$TOPIC" -m "test" -u "$MQTT_USER" -P "$MQTT_PASS" -q 1 -d --will-topic "$TOPIC" --will-payload "Disconnected" --will-qos 1 --will-retain > /dev/null 2>&1; then
|
||||
# Adapt script with MQTT settings
|
||||
sed -i "s|%%mqtt_server%%|$MQTT_HOST|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|\"%%mqtt_port%%\"|$MQTT_PORT|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_user%%|$MQTT_USER|g" /helpers/birdnet_to_mqtt.py
|
||||
sed -i "s|%%mqtt_pass%%|$MQTT_PASS|g" /helpers/birdnet_to_mqtt.py
|
||||
|
||||
# Copy script to the appropriate directory
|
||||
cp /helpers/birdnet_to_mqtt.py "$HOME"/BirdNET-Pi/scripts/utils/birdnet_to_mqtt.py
|
||||
chown pi:pi "$HOME"/BirdNET-Pi/scripts/utils/birdnet_to_mqtt.py
|
||||
chmod +x "$HOME"/BirdNET-Pi/scripts/utils/birdnet_to_mqtt.py
|
||||
|
||||
# Add hooks to the main analysis script
|
||||
sed -i "/load_global_model, run_analysis/a from utils.birdnet_to_mqtt import automatic_mqtt_publish" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
sed -i '/write_to_db(/a\ automatic_mqtt_publish(file, detection, os.path.basename(detection.file_name_extr))' "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
|
||||
else
|
||||
bashio::log.fatal "MQTT connection failed, it will not be configured"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if MQTT service is available and not disabled
|
||||
if [[ -f "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py ]] && bashio::services.available 'mqtt' && ! bashio::config.true 'MQTT_DISABLED'; then
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "MQTT addon is active on your system! Birdnet-pi is now automatically configured to send its output to MQTT"
|
||||
bashio::log.blue "MQTT user : $(bashio::services "mqtt" "username")"
|
||||
bashio::log.blue "MQTT password : $(bashio::services "mqtt" "password")"
|
||||
bashio::log.blue "MQTT broker : tcp://$(bashio::services "mqtt" "host"):$(bashio::services "mqtt" "port")"
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "Data will be posted to the topic : 'birdnet'"
|
||||
bashio::log.blue "Json data : {'Date', 'Time', 'ScientificName', 'CommonName', 'Confidence', 'SpeciesCode', 'ClipName', 'url'}"
|
||||
bashio::log.blue "---"
|
||||
|
||||
# Apply MQTT settings
|
||||
MQTT_HOST="$(bashio::services "mqtt" "host")"
|
||||
MQTT_PORT="$(bashio::services "mqtt" "port")"
|
||||
MQTT_USER="$(bashio::services "mqtt" "username")"
|
||||
MQTT_PASS="$(bashio::services "mqtt" "password")"
|
||||
|
||||
# Perform common setup steps
|
||||
common_steps
|
||||
|
||||
# Check if manual MQTT configuration is provided
|
||||
elif [[ -f "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py ]] && bashio::config.has_value "MQTT_HOST_manual" && bashio::config.has_value "MQTT_PORT_manual"; then
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "MQTT is manually configured in the addon options"
|
||||
bashio::log.blue "Birdnet-pi is now automatically configured to send its output to MQTT"
|
||||
bashio::log.green "---"
|
||||
bashio::log.blue "Data will be posted to the topic : 'birdnet'"
|
||||
bashio::log.blue "Json data : {'Date', 'Time', 'ScientificName', 'CommonName', 'Confidence', 'SpeciesCode', 'ClipName', 'url'}"
|
||||
bashio::log.blue "---"
|
||||
|
||||
# Apply manual MQTT settings
|
||||
MQTT_HOST="$(bashio::config "MQTT_HOST_manual")"
|
||||
MQTT_PORT="$(bashio::config "MQTT_PORT_manual")"
|
||||
MQTT_USER="$(bashio::config "MQTT_USER_manual")"
|
||||
MQTT_PASS="$(bashio::config "MQTT_PASSWORD_manual")"
|
||||
|
||||
# Perform common setup steps
|
||||
common_steps
|
||||
|
||||
fi
|
||||
175
birdnet-pi-zach/rootfs/etc/cont-init.d/81-modifications.sh
Executable file
175
birdnet-pi-zach/rootfs/etc/cont-init.d/81-modifications.sh
Executable file
@@ -0,0 +1,175 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash disable=SC2016
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
################
|
||||
# MODIFY WEBUI #
|
||||
################
|
||||
|
||||
bashio::log.info "Adapting webui"
|
||||
|
||||
# HA specific elements
|
||||
######################
|
||||
|
||||
if bashio::supervisor.ping 2> /dev/null; then
|
||||
# Remove services tab from webui
|
||||
echo "... removing System Controls from webui as should be used from HA"
|
||||
sed -i '/>System Controls/d' "$HOME/BirdNET-Pi/homepage/views.php"
|
||||
|
||||
# Remove pulseaudio
|
||||
echo "... disabling pulseaudio as managed by HomeAssistant"
|
||||
grep -srl "pulseaudio --start" "$HOME/BirdNET-Pi/scripts" | while read -r file; do
|
||||
sed -i "s|! pulseaudio --check|pulseaudio --check|g" "$file"
|
||||
done
|
||||
|
||||
# Check if port 80 is correctly configured
|
||||
if [ -n "$(bashio::addon.port "80")" ] && [ "$(bashio::addon.port "80")" != 80 ]; then
|
||||
bashio::log.fatal "The port 80 is enabled, but should still be 80 if you want automatic SSL certificates generation to work."
|
||||
fi
|
||||
fi
|
||||
|
||||
# General elements
|
||||
##################
|
||||
|
||||
# Avoid updates
|
||||
echo "... modifying the config to silence update indicators"
|
||||
# Remove if two lines
|
||||
if [ "$(grep -c '^SILENCE_UPDATE_INDICATOR' /config/birdnet.conf)" -ge 2 ]; then
|
||||
awk '/^SILENCE_UPDATE_INDICATOR/ { if (++n == 2) next } { print }' /config/birdnet.conf > /config/birdnet.conf.tmp && mv /config/birdnet.conf.tmp /config/birdnet.conf
|
||||
fi
|
||||
sed -i "/^SILENCE_UPDATE_INDICATOR/c SILENCE_UPDATE_INDICATOR=1" /config/birdnet.conf
|
||||
sed -i 's/"\.\$updatediv\.\"//g' "$HOME"/BirdNET-Pi/homepage/views.php
|
||||
|
||||
# Correct language labels according to birdnet.conf
|
||||
echo "... adapting labels according to birdnet.conf"
|
||||
if grep -q '^DATABASE_LANG=' /config/birdnet.conf; then
|
||||
export "$(grep -m1 '^DATABASE_LANG=' /config/birdnet.conf)"
|
||||
bashio::log.info "Setting language to ${DATABASE_LANG:-en}"
|
||||
else
|
||||
bashio::log.warning "DATABASE_LANG not found in configuration. Using default labels."
|
||||
fi
|
||||
/./"$HOME"/BirdNET-Pi/scripts/install_language_label.sh
|
||||
|
||||
# Remove Ram drive option from webui
|
||||
echo "... removing Ram drive from webui as it is handled from HA"
|
||||
if grep -q "Ram drive" "$HOME/BirdNET-Pi/scripts/service_controls.php"; then
|
||||
sed -i '/Ram drive/{n;s/center"/center" style="display: none;"/;}' "$HOME/BirdNET-Pi/scripts/service_controls.php"
|
||||
sed -i '/Ram drive/d' "$HOME/BirdNET-Pi/scripts/service_controls.php"
|
||||
fi
|
||||
|
||||
# Allow symlinks
|
||||
echo "... ensuring symlinks work"
|
||||
for files in "$HOME"/BirdNET-Pi/scripts/*.sh; do
|
||||
sed -i "s|find |find -L |g" "$files"
|
||||
sed -i "s|find -L -L |find -L |g" "$files"
|
||||
done
|
||||
|
||||
# Correct services to start as user pi
|
||||
echo "... updating services to start as user pi"
|
||||
if ! grep -q "/usr/bin/sudo" "$HOME/BirdNET-Pi/templates/birdnet_analysis.service"; then
|
||||
while IFS= read -r file; do
|
||||
if [[ "$(basename "$file")" != "birdnet_log.service" ]]; then
|
||||
sed -i "s|ExecStart=|ExecStart=/usr/bin/sudo -u pi |g" "$file"
|
||||
fi
|
||||
done < <(find "$HOME/BirdNET-Pi/templates/" -name "*net*.service" -print)
|
||||
fi
|
||||
|
||||
# Allow pulseaudio system
|
||||
echo "... allow pulseaudio as root as backup"
|
||||
sed -i 's#pulseaudio --start#pulseaudio --start || pulseaudio --system#g' "$HOME"/BirdNET-Pi/scripts/birdnet_recording.sh
|
||||
|
||||
# Send services log to container logs
|
||||
echo "... redirecting services logs to container logs"
|
||||
while IFS= read -r file; do
|
||||
sed -i "/StandardError/d" "$file"
|
||||
sed -i "/StandardOutput/d" "$file"
|
||||
sed -i "/\[Service/a StandardError=append:/proc/1/fd/1" "$file"
|
||||
sed -i "/\[Service/a StandardOutput=append:/proc/1/fd/1" "$file"
|
||||
done < <(find "$HOME/BirdNET-Pi/templates/" -name "*.service" -print)
|
||||
|
||||
# Preencode API key
|
||||
if [[ -f "$HOME/BirdNET-Pi/scripts/common.php" ]] && ! grep -q "221160312" "$HOME/BirdNET-Pi/scripts/common.php"; then
|
||||
sed -i "/return \$_SESSION\['my_config'\];/i\ \ \ \ if (isset(\$_SESSION\['my_config'\]) \&\& empty(\$_SESSION\['my_config'\]\['FLICKR_API_KEY'\])) {\n\ \ \ \ \ \ \ \ \$_SESSION\['my_config'\]\['FLICKR_API_KEY'\] = \"221160312e1c22\";\n\ \ \ \ }" "$HOME"/BirdNET-Pi/scripts/common.php
|
||||
sed -i "s|e1c22|e1c22ec60ecf336951b0e77|g" "$HOME"/BirdNET-Pi/scripts/common.php
|
||||
fi
|
||||
|
||||
# Correct log services to show /proc/1/fd/1
|
||||
echo "... redirecting birdnet_log service output to /logs"
|
||||
sed -i "/User=pi/d" "$HOME/BirdNET-Pi/templates/birdnet_log.service"
|
||||
sed -i "s|birdnet_log.sh|cat /proc/1/fd/1|g" "$HOME/BirdNET-Pi/templates/birdnet_log.service"
|
||||
|
||||
# Correct backup script
|
||||
if [[ -f "$HOME/BirdNET-Pi/scripts/backup_data.sh" ]]; then
|
||||
echo "... correct backup script"
|
||||
sed -i "/PHP_SERVICE=/c PHP_SERVICE=\$(systemctl list-unit-files -t service --no-pager | grep 'php' | grep 'fpm' | awk '{print \$1}')" "$HOME/BirdNET-Pi/scripts/backup_data.sh"
|
||||
fi
|
||||
|
||||
# Caddyfile modifications
|
||||
echo "... modifying Caddyfile configurations"
|
||||
caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||
#Change port to leave 80 free for certificate requests
|
||||
if ! grep -q "http://:8081" /etc/caddy/Caddyfile; then
|
||||
sed -i "s|http://|http://:8081|g" /etc/caddy/Caddyfile
|
||||
sed -i "s|http://|http://:8081|g" "$HOME/BirdNET-Pi/scripts/update_caddyfile.sh"
|
||||
if [ -f /etc/caddy/Caddyfile.original ]; then
|
||||
rm /etc/caddy/Caddyfile.original
|
||||
fi
|
||||
fi
|
||||
# Fix weekly report
|
||||
sed -i 's|localhost|localhost:8081|g' "$HOME/BirdNET-Pi/scripts/weekly_report.sh"
|
||||
|
||||
# Correct webui paths
|
||||
echo "... correcting webui paths"
|
||||
if ! grep -q "/stats/" "$HOME/BirdNET-Pi/homepage/views.php"; then
|
||||
sed -i "s|/stats|/stats/|g" "$HOME/BirdNET-Pi/homepage/views.php"
|
||||
sed -i "s|/log|/log/|g" "$HOME/BirdNET-Pi/homepage/views.php"
|
||||
fi
|
||||
|
||||
# Correct systemctl path
|
||||
if [ -f /helpers/systemctl ] && [ -f /helpers/journalctl ]; then
|
||||
echo "... updating systemctl and journalctl"
|
||||
cp -rf /helpers/systemctl /bin/systemctl
|
||||
cp -rf /helpers/journalctl /bin/journalctl
|
||||
chown pi:pi /bin/systemctl /bin/journalctl
|
||||
chmod a+x /bin/systemctl /bin/journalctl
|
||||
fi
|
||||
|
||||
# Allow reverse proxy for streamlit
|
||||
echo "... allow reverse proxy for streamlit"
|
||||
sed -i "s|plotly_streamlit.py --browser.gatherUsageStats|plotly_streamlit.py --server.enableXsrfProtection=false --server.enableCORS=false --browser.gatherUsageStats|g" "$HOME/BirdNET-Pi/templates/birdnet_stats.service"
|
||||
|
||||
# Clean saved mp3 files
|
||||
if [[ -f "$HOME/BirdNET-Pi/scripts/utils/reporting.py" ]]; then
|
||||
echo "... add highpass and lowpass to sox extracts"
|
||||
sed -i "s|f'={stop}']|f'={stop}', 'highpass', '250']|g" "$HOME/BirdNET-Pi/scripts/utils/reporting.py"
|
||||
fi
|
||||
|
||||
# Correct timedatectl path
|
||||
echo "... updating timedatectl path"
|
||||
if [[ -f /helpers/timedatectl ]]; then
|
||||
mv /helpers/timedatectl /usr/bin/timedatectl
|
||||
chown pi:pi /usr/bin/timedatectl
|
||||
chmod a+x /usr/bin/timedatectl
|
||||
fi
|
||||
|
||||
# Set RECS_DIR
|
||||
echo "... setting RECS_DIR to /tmp"
|
||||
grep -rl "RECS_DIR" "$HOME" --exclude="*.php" | while read -r file; do
|
||||
sed -i "s|conf\['RECS_DIR'\]|'/tmp'|g" "$file"
|
||||
sed -i "s|\$RECS_DIR|/tmp|g" "$file"
|
||||
sed -i "s|\${RECS_DIR}|/tmp|g" "$file"
|
||||
sed -i "/^RECS_DIR=/c RECS_DIR=/tmp" "$file"
|
||||
sed -i "/^\$RECS_DIR=/c \$RECS_DIR=/tmp" "$file"
|
||||
done
|
||||
mkdir -p /tmp
|
||||
76
birdnet-pi-zach/rootfs/etc/cont-init.d/91-nginx_ingress.sh
Executable file
76
birdnet-pi-zach/rootfs/etc/cont-init.d/91-nginx_ingress.sh
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
#################
|
||||
# NGINX SETTING #
|
||||
#################
|
||||
|
||||
# Variables
|
||||
ingress_port=$(bashio::addon.ingress_port)
|
||||
ingress_interface=$(bashio::addon.ip_address)
|
||||
ingress_entry=$(bashio::addon.ingress_entry)
|
||||
|
||||
# Quits if ingress is not active
|
||||
if [[ "$ingress_entry" != "/api"* ]]; then
|
||||
bashio::log.info "Ingress entry is not set, exiting configuration."
|
||||
sed -i "1a sleep infinity" /custom-services.d/02-nginx.sh
|
||||
exit 0
|
||||
fi
|
||||
|
||||
bashio::log.info "Adapting for ingress"
|
||||
echo "... setting up nginx"
|
||||
|
||||
# Check if the NGINX configuration file exists
|
||||
nginx_conf="/etc/nginx/servers/ingress.conf"
|
||||
if [ -f "$nginx_conf" ]; then
|
||||
sed -i "s/%%port%%/${ingress_port}/g" "$nginx_conf"
|
||||
sed -i "s/%%interface%%/${ingress_interface}/g" "$nginx_conf"
|
||||
sed -i "s|%%ingress_entry%%|${ingress_entry}|g" "$nginx_conf"
|
||||
else
|
||||
bashio::log.error "NGINX configuration file not found: $nginx_conf"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Disable log
|
||||
sed -i "/View Log/d" "$HOME/BirdNET-Pi/homepage/views.php"
|
||||
|
||||
echo "... ensuring restricted area access"
|
||||
echo "${ingress_entry}" > /ingress_url
|
||||
|
||||
# Modify PHP file safely
|
||||
php_file="$HOME/BirdNET-Pi/scripts/common.php"
|
||||
if [ -f "$php_file" ]; then
|
||||
sed -i "/function is_authenticated/a if (strpos(\$_SERVER['HTTP_REFERER'], '/api/hassio_ingress') !== false && strpos(\$_SERVER['HTTP_REFERER'], trim(file_get_contents('/ingress_url'))) !== false) { \$ret = true; return \$ret; }" "$php_file"
|
||||
else
|
||||
bashio::log.warning "PHP file not found: $php_file"
|
||||
fi
|
||||
|
||||
echo "... adapting Caddyfile for ingress"
|
||||
chmod +x /helpers/caddy_ingress.sh
|
||||
|
||||
# Correct script execution
|
||||
/helpers/caddy_ingress.sh
|
||||
|
||||
# Correct API images
|
||||
sed -i "s|localhost|localhost:8082|g" "$HOME/BirdNET-Pi/scripts/utils/notifications.py"
|
||||
|
||||
# Update the Caddyfile if update script exists
|
||||
caddy_update_script="$HOME/BirdNET-Pi/scripts/update_caddyfile.sh"
|
||||
if [ -f "$caddy_update_script" ]; then
|
||||
sed -i "/sudo caddy fmt --overwrite/i /helpers/caddy_ingress.sh" "$caddy_update_script"
|
||||
else
|
||||
bashio::log.error "Caddy update script not found: $caddy_update_script"
|
||||
exit 1
|
||||
fi
|
||||
49
birdnet-pi-zach/rootfs/etc/cont-init.d/92-ssl.sh
Executable file
49
birdnet-pi-zach/rootfs/etc/cont-init.d/92-ssl.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
###############
|
||||
# SSL SETTING #
|
||||
###############
|
||||
|
||||
if bashio::config.true 'ssl'; then
|
||||
bashio::log.info "SSL is enabled using addon options, setting up NGINX and Caddy."
|
||||
|
||||
# Check required SSL configurations
|
||||
bashio::config.require.ssl
|
||||
certfile=$(bashio::config 'certfile')
|
||||
keyfile=$(bashio::config 'keyfile')
|
||||
|
||||
# Ensure Caddyfile exists before modifying
|
||||
caddyfile="/etc/caddy/Caddyfile"
|
||||
if [ -f "$caddyfile" ]; then
|
||||
sed -i "2a\ tls /ssl/${certfile} /ssl/${keyfile}" "$caddyfile"
|
||||
sed -i "s|http://:8081|https://:8081|g" "$caddyfile"
|
||||
else
|
||||
bashio::log.error "Caddyfile not found at $caddyfile, skipping SSL configuration."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure update_caddyfile.sh exists before modifying
|
||||
update_script="$HOME/BirdNET-Pi/scripts/update_caddyfile.sh"
|
||||
if [ -f "$update_script" ]; then
|
||||
sed -i "s|http://:8081|https://:8081|g" "$update_script"
|
||||
if ! grep -q "tls /ssl/${certfile} /ssl/${keyfile}" "$update_script"; then
|
||||
sed -i "/https:/a\ tls /ssl/${certfile} /ssl/${keyfile}" "$update_script"
|
||||
fi
|
||||
else
|
||||
bashio::log.error "Update script not found: $update_script, skipping SSL setup for update."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
37
birdnet-pi-zach/rootfs/etc/cont-init.d/98-oldcpu.sh
Executable file
37
birdnet-pi-zach/rootfs/etc/cont-init.d/98-oldcpu.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash disable=SC1091
|
||||
set -e
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
######################
|
||||
# INSTALL TENSORFLOW #
|
||||
######################
|
||||
|
||||
# Check if the CPU supports AVX2
|
||||
if [[ "$(uname -m)" = "x86_64" ]]; then
|
||||
if lscpu | grep -q "Flags"; then
|
||||
if ! lscpu | grep -q "avx2"; then
|
||||
bashio::log.warning "NON SUPPORTED CPU DETECTED"
|
||||
bashio::log.warning "Your cpu doesn't support avx2, the analyzer service will likely won't work"
|
||||
bashio::log.warning "Trying to install tensorflow instead of tflite_runtime instead. This might take some time (up to 5 minutes)."
|
||||
bashio::log.warning "You could try also Birdnet-Go which should supports your cpu"
|
||||
source /home/pi/BirdNET-Pi/birdnet/bin/activate
|
||||
mkdir -p /home/pi/.cache/pip || true &> /dev/null
|
||||
chmod 755 /home/pi/.cache/pip || true &> /dev/null
|
||||
pip3 uninstall -y tflite_runtime
|
||||
pip install --upgrade packaging==23.2
|
||||
pip3 install --upgrade --force-reinstall "https://github.com/snowzach/tensorflow-multiarch/releases/download/v2.16.1/tensorflow-2.16.1-cp311-cp311-linux_x86_64.whl"
|
||||
deactivate
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
107
birdnet-pi-zach/rootfs/etc/cont-init.d/99-run.sh
Executable file
107
birdnet-pi-zach/rootfs/etc/cont-init.d/99-run.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/command/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
|
||||
set -eu
|
||||
|
||||
##################
|
||||
# ALLOW RESTARTS #
|
||||
##################
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == /etc/cont-init.d/* ]]; then
|
||||
mkdir -p /etc/scripts-init
|
||||
sed -i "s|/etc/cont-init.d|/etc/scripts-init|g" /ha_entrypoint.sh
|
||||
sed -i "/ rm/d" /ha_entrypoint.sh
|
||||
cp "${BASH_SOURCE[0]}" /etc/scripts-init/
|
||||
fi
|
||||
|
||||
##############
|
||||
# SET SYSTEM #
|
||||
##############
|
||||
|
||||
# Set password
|
||||
bashio::log.info "Setting password for the user pi"
|
||||
if bashio::config.has_value "pi_password"; then
|
||||
PI_PASSWORD="$(bashio::config "pi_password")"
|
||||
elif [[ -n "${pi_password:-}" ]]; then
|
||||
PI_PASSWORD="${pi_password}"
|
||||
fi
|
||||
|
||||
if [[ -n "${PI_PASSWORD:-}" ]]; then
|
||||
echo "pi:${PI_PASSWORD}" | chpasswd
|
||||
bashio::log.info "Password set successfully for user pi."
|
||||
else
|
||||
# Set empty password to allow web terminal login when no password is configured
|
||||
passwd -d pi
|
||||
bashio::log.info "No password specified for user pi. Enabled passwordless login."
|
||||
fi
|
||||
|
||||
# Use timezone defined in add-on options if available
|
||||
bashio::log.info "Setting timezone :"
|
||||
if bashio::config.has_value 'TZ'; then
|
||||
TZ_VALUE="$(bashio::config 'TZ')"
|
||||
if timedatectl set-timezone "$TZ_VALUE"; then
|
||||
echo "... timezone set to $TZ_VALUE as defined in add-on options (BirdNET config ignored)."
|
||||
else
|
||||
bashio::log.warning "Couldn't set timezone to $TZ_VALUE. Refer to the list of valid timezones: https://manpages.ubuntu.com/manpages/focal/man3/DateTime::TimeZone::Catalog.3pm.html"
|
||||
timedatectl set-ntp true &> /dev/null
|
||||
fi
|
||||
# Use BirdNET-defined timezone if no add-on option is provided
|
||||
elif [ -f /data/timezone ]; then
|
||||
BIRDN_CONFIG_TZ="$(cat /data/timezone)"
|
||||
timedatectl set-ntp false &> /dev/null
|
||||
if timedatectl set-timezone "$BIRDN_CONFIG_TZ"; then
|
||||
echo "... set to $BIRDN_CONFIG_TZ as defined in BirdNET config."
|
||||
else
|
||||
bashio::log.warning "Couldn't set timezone to $BIRDN_CONFIG_TZ. Reverting to automatic timezone."
|
||||
timedatectl set-ntp true &> /dev/null
|
||||
fi
|
||||
# Fallback to automatic timezone if no manual settings are found
|
||||
else
|
||||
if timedatectl set-ntp true &> /dev/null; then
|
||||
bashio::log.info "... automatic timezone enabled."
|
||||
else
|
||||
bashio::log.fatal "Couldn't set automatic timezone! Please set a manual one from the options."
|
||||
fi
|
||||
fi || true
|
||||
|
||||
# Use ALSA CARD defined in add-on options if available
|
||||
if [ -n "${ALSA_CARD:-}" ]; then
|
||||
bashio::log.warning "ALSA_CARD is defined, the birdnet.conf is adapt to use device $ALSA_CARD"
|
||||
for file in "$HOME"/BirdNET-Pi/birdnet.conf /config/birdnet.conf; do
|
||||
if [ -f "$file" ]; then
|
||||
sed -i "/^REC_CARD/c\REC_CARD=$ALSA_CARD" "$file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Define permissions for audio
|
||||
AUDIO_GID=$(stat -c %g /dev/snd/* | head -n1) \
|
||||
&& (groupmod -o -g "$AUDIO_GID" audio 2> /dev/null || groupadd -o -g "$AUDIO_GID" audio || true) \
|
||||
&& usermod -aG audio "${USER:-pi}" || true
|
||||
|
||||
# Fix timezone as per installer
|
||||
CURRENT_TIMEZONE="$(timedatectl show --value --property=Timezone)"
|
||||
[ -f /etc/timezone ] && echo "$CURRENT_TIMEZONE" | sudo tee /etc/timezone > /dev/null
|
||||
|
||||
bashio::log.info "Starting system services"
|
||||
|
||||
bashio::log.info "Starting cron service"
|
||||
systemctl start cron > /dev/null
|
||||
|
||||
bashio::log.info "Starting dbus service"
|
||||
service dbus start > /dev/null
|
||||
|
||||
bashio::log.info "Starting BirdNET-Pi services"
|
||||
chmod +x "$HOME/BirdNET-Pi/scripts/restart_services.sh" > /dev/null
|
||||
"$HOME/BirdNET-Pi/scripts/restart_services.sh" > /dev/null
|
||||
|
||||
# Start livestream services if enabled in configuration
|
||||
if bashio::config.true "LIVESTREAM_BOOT_ENABLED"; then
|
||||
echo "... starting livestream services"
|
||||
systemctl enable icecast2 > /dev/null
|
||||
systemctl start icecast2.service > /dev/null
|
||||
systemctl enable --now livestream.service > /dev/null
|
||||
fi
|
||||
|
||||
# Start
|
||||
bashio::log.info "✅ Setup complete."
|
||||
96
birdnet-pi-zach/rootfs/etc/nginx/includes/mime.types
Normal file
96
birdnet-pi-zach/rootfs/etc/nginx/includes/mime.types
Normal file
@@ -0,0 +1,96 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
16
birdnet-pi-zach/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
16
birdnet-pi-zach/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
1
birdnet-pi-zach/rootfs/etc/nginx/includes/resolver.conf
Normal file
1
birdnet-pi-zach/rootfs/etc/nginx/includes/resolver.conf
Normal file
@@ -0,0 +1 @@
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
@@ -0,0 +1,6 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
@@ -0,0 +1,9 @@
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
|
||||
ssl_ecdh_curve secp384r1;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
3
birdnet-pi-zach/rootfs/etc/nginx/includes/upstream.conf
Normal file
3
birdnet-pi-zach/rootfs/etc/nginx/includes/upstream.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:80;
|
||||
}
|
||||
78
birdnet-pi-zach/rootfs/etc/nginx/nginx.conf
Normal file
78
birdnet-pi-zach/rootfs/etc/nginx/nginx.conf
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
# Run nginx in foreground.
|
||||
daemon off;
|
||||
|
||||
# This is run inside Docker.
|
||||
user root;
|
||||
|
||||
# Pid storage location.
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
# Set number of worker processes.
|
||||
worker_processes auto;
|
||||
|
||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||
pcre_jit on;
|
||||
|
||||
# Write error log to Hass.io add-on log.
|
||||
error_log /proc/1/fd/1 error;
|
||||
|
||||
# Load allowed environment vars
|
||||
env HASSIO_TOKEN;
|
||||
|
||||
# Load dynamic modules.
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
# Max num of simultaneous connections by a worker process.
|
||||
events {
|
||||
worker_connections 8192;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
|
||||
# https://emby.media/community/index.php?/topic/93074-how-to-emby-with-nginx-with-windows-specific-tips-and-csp-options/
|
||||
server_names_hash_bucket_size 64;
|
||||
gzip_disable "msie6";
|
||||
gzip_comp_level 6;
|
||||
gzip_min_length 1100;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_proxied any;
|
||||
gzip_types
|
||||
text/plain
|
||||
text/css
|
||||
text/js
|
||||
text/xml
|
||||
text/javascript
|
||||
application/javascript
|
||||
application/x-javascript
|
||||
application/json
|
||||
application/xml
|
||||
application/rss+xml
|
||||
image/svg+xml;
|
||||
proxy_connect_timeout 1h;
|
||||
|
||||
log_format hassio '[$time_local] $status '
|
||||
'$http_x_forwarded_for($remote_addr) '
|
||||
'$request ($http_user_agent)';
|
||||
|
||||
access_log /proc/1/fd/1 hassio;
|
||||
client_max_body_size 4G;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/resolver.conf;
|
||||
include /etc/nginx/includes/upstream.conf;
|
||||
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
||||
41
birdnet-pi-zach/rootfs/etc/nginx/servers/ingress.conf
Normal file
41
birdnet-pi-zach/rootfs/etc/nginx/servers/ingress.conf
Normal file
@@ -0,0 +1,41 @@
|
||||
server {
|
||||
listen %%interface%%:%%port%% default_server;
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
|
||||
proxy_buffering off;
|
||||
auth_basic_user_file /home/pi/.htpasswd;
|
||||
|
||||
location / {
|
||||
# Proxy pass
|
||||
proxy_pass http://localhost:8082;
|
||||
|
||||
# Next three lines allow websockets
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Adjust any Location headers in backend redirects
|
||||
absolute_redirect off;
|
||||
proxy_redirect /stats %%ingress_entry%%/stats;
|
||||
proxy_redirect /log %%ingress_entry%%/log;
|
||||
proxy_redirect /terminal %%ingress_entry%%/terminal;
|
||||
|
||||
# Correct base_url
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once off;
|
||||
sub_filter_types *;
|
||||
sub_filter /spectrogram %%ingress_entry%%/spectrogram;
|
||||
sub_filter /By_Date/ %%ingress_entry%%/By_Date/;
|
||||
sub_filter /Charts/ %%ingress_entry%%/Charts/;
|
||||
sub_filter /stats/ %%ingress_entry%%/stats/;
|
||||
sub_filter /log/ %%ingress_entry%%/log/;
|
||||
sub_filter /terminal/ %%ingress_entry%%/terminal/;
|
||||
sub_filter "url = '/" "url = '%%ingress_entry%%/";
|
||||
sub_filter /todays %%ingress_entry%%/todays;
|
||||
sub_filter href=\"/ href=\"%%ingress_entry%%/;
|
||||
sub_filter src=\"/ src=\"%%ingress_entry%%/;
|
||||
sub_filter hx-get=\"/ hx-get=\"%%ingress_entry%%/;
|
||||
sub_filter action=\"/ action=\"%%ingress_entry%%/;
|
||||
}
|
||||
}
|
||||
141
birdnet-pi-zach/rootfs/helpers/birdnet_to_mqtt.py
Normal file
141
birdnet-pi-zach/rootfs/helpers/birdnet_to_mqtt.py
Normal file
@@ -0,0 +1,141 @@
|
||||
#! /usr/bin/env python3
|
||||
# birdnet_to_mqtt.py
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
import requests
|
||||
|
||||
utils_path = os.path.expanduser("~/BirdNET-Pi/scripts/utils")
|
||||
sys.path.append(utils_path)
|
||||
|
||||
from helpers import get_settings
|
||||
|
||||
# Setup basic configuration for logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Used in flickrimage
|
||||
flickr_images = {}
|
||||
conf = get_settings()
|
||||
settings_dict = dict(conf)
|
||||
|
||||
# MQTT server configuration
|
||||
mqtt_server = "%%mqtt_server%%"
|
||||
mqtt_user = "%%mqtt_user%%"
|
||||
mqtt_pass = "%%mqtt_pass%%"
|
||||
mqtt_port = "%%mqtt_port%%"
|
||||
mqtt_topic = "birdnet"
|
||||
bird_lookup_url_base = "http://en.wikipedia.org/wiki/"
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc): # , properties=None):
|
||||
"""Callback for when the client receives a CONNACK response from the server."""
|
||||
if rc == 0:
|
||||
log.info("Connected to MQTT Broker!")
|
||||
else:
|
||||
log.error(f"Failed to connect, return code {rc}\n")
|
||||
|
||||
|
||||
def get_bird_code(scientific_name):
|
||||
with open("/home/pi/BirdNET-Pi/scripts/ebird.php", "r") as file:
|
||||
data = file.read()
|
||||
|
||||
array_str = re.search(r"\$ebirds = \[(.*?)\];", data, re.DOTALL).group(1)
|
||||
|
||||
bird_dict = {
|
||||
re.search(r'"(.*?)"', line).group(1): re.search(r'=> "(.*?)"', line).group(1)
|
||||
for line in array_str.split("\n")
|
||||
if "=>" in line
|
||||
}
|
||||
|
||||
return bird_dict.get(scientific_name)
|
||||
|
||||
|
||||
def automatic_mqtt_publish(file, detection, path):
|
||||
bird = {}
|
||||
bird["Date"] = detection.date
|
||||
bird["Time"] = detection.time
|
||||
bird["ScientificName"] = detection.scientific_name.replace("_", " ")
|
||||
bird["CommonName"] = detection.common_name
|
||||
bird["Confidence"] = detection.confidence
|
||||
bird["SpeciesCode"] = get_bird_code(detection.scientific_name)
|
||||
bird["ClipName"] = path
|
||||
bird["url"] = bird_lookup_url_base + detection.scientific_name.replace(" ", "_")
|
||||
|
||||
# Flickimage
|
||||
image_url = ""
|
||||
common_name = detection.common_name
|
||||
if len(settings_dict.get("FLICKR_API_KEY")) > 0:
|
||||
if common_name not in flickr_images:
|
||||
try:
|
||||
headers = {"User-Agent": "Python_Flickr/1.0"}
|
||||
url = (
|
||||
"https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key="
|
||||
+ str(settings_dict.get("FLICKR_API_KEY"))
|
||||
+ "&text="
|
||||
+ str(common_name)
|
||||
+ " bird&sort=relevance&per_page=5&media=photos&format=json&license=2%2C3%2C4%2C5%2C6%2C9"
|
||||
+ "&nojsoncallback=1"
|
||||
)
|
||||
resp = requests.get(url=url, headers=headers, timeout=10)
|
||||
|
||||
resp.encoding = "utf-8"
|
||||
data = resp.json()["photos"]["photo"][0]
|
||||
|
||||
image_url = (
|
||||
"https://farm"
|
||||
+ str(data["farm"])
|
||||
+ ".static.flickr.com/"
|
||||
+ str(data["server"])
|
||||
+ "/"
|
||||
+ str(data["id"])
|
||||
+ "_"
|
||||
+ str(data["secret"])
|
||||
+ "_n.jpg"
|
||||
)
|
||||
flickr_images[common_name] = image_url
|
||||
except Exception as e:
|
||||
print("FLICKR API ERROR: " + str(e))
|
||||
image_url = ""
|
||||
else:
|
||||
image_url = flickr_images[common_name]
|
||||
|
||||
bird["FlickrImage"] = image_url
|
||||
|
||||
json_bird = json.dumps(bird)
|
||||
mqttc.reconnect()
|
||||
mqttc.publish(mqtt_topic, json_bird, 1)
|
||||
log.info("Posted to MQTT: ok")
|
||||
|
||||
# Create MQTT client using legacy callback API when available for
|
||||
# compatibility with paho-mqtt >= 2.0
|
||||
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
||||
|
||||
if callback_api is not None:
|
||||
# paho-mqtt >= 2.0: first argument is callback_api_version
|
||||
mqttc = mqtt.Client(callback_api.VERSION1, client_id="birdnet_mqtt")
|
||||
else:
|
||||
# paho-mqtt < 2.0: old signature, first argument is client_id
|
||||
mqttc = mqtt.Client(client_id="birdnet_mqtt")
|
||||
|
||||
mqttc.username_pw_set(mqtt_user, mqtt_pass)
|
||||
mqttc.on_connect = on_connect
|
||||
|
||||
try:
|
||||
mqttc.connect(mqtt_server, mqtt_port)
|
||||
mqttc.loop_start()
|
||||
|
||||
# Assuming `file` and `detections` are provided from somewhere
|
||||
# automatic_mqtt_publish(file, detections)
|
||||
|
||||
except Exception as e:
|
||||
log.error("Cannot post mqtt: %s", e)
|
||||
|
||||
finally:
|
||||
mqttc.loop_stop()
|
||||
mqttc.disconnect()
|
||||
26
birdnet-pi-zach/rootfs/helpers/caddy_ingress.sh
Executable file
26
birdnet-pi-zach/rootfs/helpers/caddy_ingress.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Get values
|
||||
set +u
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/birdnet/birdnet.conf
|
||||
|
||||
# Create ingress configuration for Caddyfile
|
||||
cat << EOF >> /etc/caddy/Caddyfile
|
||||
:8082 {
|
||||
root * ${EXTRACTED}
|
||||
file_server browse
|
||||
handle /By_Date/* {
|
||||
file_server browse
|
||||
}
|
||||
handle /Charts/* {
|
||||
file_server browse
|
||||
}
|
||||
reverse_proxy /stream localhost:8000
|
||||
php_fastcgi unix//run/php/php-fpm.sock
|
||||
reverse_proxy /log* localhost:8080
|
||||
reverse_proxy /stats* localhost:8501
|
||||
reverse_proxy /terminal* localhost:8888
|
||||
}
|
||||
EOF
|
||||
107
birdnet-pi-zach/rootfs/helpers/timedatectl
Normal file
107
birdnet-pi-zach/rootfs/helpers/timedatectl
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to show the current timezone using two alternative methods
|
||||
show_timezone() {
|
||||
if [ -f /data/timezone ]; then
|
||||
cat /data/timezone
|
||||
elif [ -f /etc/timezone ]; then
|
||||
cat /etc/timezone
|
||||
elif [ -f /etc/localtime ]; then
|
||||
readlink /etc/localtime | sed 's|/usr/share/zoneinfo/||'
|
||||
else
|
||||
echo "Cannot determine timezone."
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to set the timezone
|
||||
set_timezone() {
|
||||
local new_timezone="$1"
|
||||
if [ ! -f "/usr/share/zoneinfo/$new_timezone" ]; then
|
||||
echo "Invalid timezone: $new_timezone"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$new_timezone" >/data/timezone
|
||||
echo "$new_timezone" >/etc/timezone
|
||||
ln -sf "/usr/share/zoneinfo/$new_timezone" /etc/localtime
|
||||
|
||||
# Update /etc/environment if it exists
|
||||
if [ -f /etc/environment ]; then
|
||||
sed -i "/^TZ=/c\TZ=$new_timezone" /etc/environment
|
||||
fi
|
||||
|
||||
# Update s6 container environment if it exists
|
||||
if [ -d /var/run/s6/container_environment ]; then
|
||||
echo "$new_timezone" >/var/run/s6/container_environment/TZ
|
||||
fi
|
||||
|
||||
echo "Timezone set to: $new_timezone"
|
||||
}
|
||||
|
||||
# Function to enable or disable NTP
|
||||
set_ntp() {
|
||||
case "$1" in
|
||||
"false")
|
||||
systemctl stop systemd-timesyncd
|
||||
systemctl disable systemd-timesyncd
|
||||
echo "NTP disabled"
|
||||
;;
|
||||
"true")
|
||||
systemctl start systemd-timesyncd
|
||||
systemctl enable systemd-timesyncd
|
||||
|
||||
# Remove the /data/timezone file when NTP is enabled
|
||||
if [ -f /data/timezone ]; then
|
||||
rm -f /data/timezone
|
||||
echo "Timezone configuration file /data/timezone deleted."
|
||||
fi
|
||||
|
||||
echo "NTP enabled"
|
||||
;;
|
||||
*)
|
||||
echo "Invalid argument for set-ntp. Use 'false' or 'true'."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to show detailed time settings
|
||||
show_time_details() {
|
||||
local local_time
|
||||
local utc_time
|
||||
local time_zone
|
||||
local ntp_status="no"
|
||||
local ntp_service="inactive"
|
||||
|
||||
local_time="$(date)"
|
||||
utc_time="$(date -u)"
|
||||
time_zone="$(show_timezone)"
|
||||
|
||||
# Check if NTP is used
|
||||
if systemctl is-active --quiet systemd-timesyncd; then
|
||||
ntp_status="yes"
|
||||
ntp_service="active"
|
||||
fi
|
||||
|
||||
# Print the information
|
||||
echo "Local time: $local_time"
|
||||
echo "Universal time: $utc_time"
|
||||
echo "Time zone: $time_zone"
|
||||
echo "Network time on: $ntp_status"
|
||||
echo "NTP service: $ntp_service"
|
||||
}
|
||||
|
||||
# Main script logic
|
||||
case "$1" in
|
||||
"set-ntp")
|
||||
set_ntp "$2"
|
||||
;;
|
||||
"show")
|
||||
show_timezone
|
||||
;;
|
||||
"set-timezone")
|
||||
set_timezone "$2"
|
||||
;;
|
||||
*)
|
||||
show_time_details
|
||||
;;
|
||||
esac
|
||||
268
birdnet-pi-zach/rootfs/helpers/timedatectl2
Normal file
268
birdnet-pi-zach/rootfs/helpers/timedatectl2
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# A simple bash script to replicate essential "timedatectl" functionality:
|
||||
# - Show current time and time zone
|
||||
# - List available time zones
|
||||
# - Set time or time zone
|
||||
# - Enable/disable/verify NTP
|
||||
# - Configure RTC as localtime or UTC
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Utility functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
print_usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") COMMAND [ARGS...]
|
||||
|
||||
Commands:
|
||||
status Show current time/date, timezone, RTC mode, NTP status
|
||||
set-time [TIME] Set system clock to TIME (e.g. "2025-03-21 18:00:00")
|
||||
set-timezone [ZONE] Set system timezone to ZONE (Region/City)
|
||||
list-timezones List all known timezones in /usr/share/zoneinfo
|
||||
set-local-rtc [0|1] Set whether RTC is in local time (1) or UTC (0)
|
||||
set-ntp [true|false] Enable or disable systemd-timesyncd (if available)
|
||||
|
||||
Examples:
|
||||
$(basename "$0") status
|
||||
$(basename "$0") set-time "2025-03-21 18:00:00"
|
||||
$(basename "$0") set-timezone America/New_York
|
||||
$(basename "$0") set-local-rtc 1
|
||||
$(basename "$0") set-ntp true
|
||||
|
||||
Note: Most operations require root privileges. Use sudo if necessary.
|
||||
EOF
|
||||
}
|
||||
|
||||
# Safely exit on errors
|
||||
abort() {
|
||||
echo "Error: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Functions for each major operation
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
show_status() {
|
||||
echo "=== System Time/Date ==="
|
||||
date
|
||||
|
||||
echo
|
||||
echo "=== Time Zone ==="
|
||||
# Attempt to parse the symlink at /etc/localtime
|
||||
# On many systems, /etc/localtime is a symlink to /usr/share/zoneinfo/Region/City
|
||||
if [ -L /etc/localtime ]; then
|
||||
local tz_target
|
||||
tz_target=$(readlink -f /etc/localtime)
|
||||
# Remove the /usr/share/zoneinfo/ part to display just Region/City
|
||||
local tz_name
|
||||
tz_name="${tz_target#/usr/share/zoneinfo/}"
|
||||
echo "Time zone: $tz_name"
|
||||
else
|
||||
# Some distros have /etc/localtime as a copy of the zone file
|
||||
# Try to read /etc/timezone as a fallback (Debian-based)
|
||||
if [ -f /etc/timezone ]; then
|
||||
echo "Time zone: $(cat /etc/timezone)"
|
||||
else
|
||||
echo "Time zone: Unknown (not a symlink, and /etc/timezone missing)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== RTC in local TZ? ==="
|
||||
# If /etc/adjtime exists and the last line is "LOCAL", that typically indicates localtime
|
||||
# If it’s "UTC" or absent, the RTC is typically in UTC.
|
||||
if [ -f /etc/adjtime ]; then
|
||||
local rtc_mode
|
||||
rtc_mode=$(tail -n 1 /etc/adjtime)
|
||||
if [ "$rtc_mode" = "LOCAL" ]; then
|
||||
echo "RTC is in local time."
|
||||
else
|
||||
echo "RTC is in UTC."
|
||||
fi
|
||||
else
|
||||
echo "Cannot determine RTC mode (no /etc/adjtime)."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== NTP Service Status ==="
|
||||
# Attempt to detect if systemd-timesyncd is enabled/active
|
||||
if command -v systemctl &>/dev/null; then
|
||||
if systemctl is-enabled systemd-timesyncd &>/dev/null; then
|
||||
echo "systemd-timesyncd service is enabled."
|
||||
else
|
||||
echo "systemd-timesyncd service is disabled."
|
||||
fi
|
||||
|
||||
if systemctl is-active systemd-timesyncd &>/dev/null; then
|
||||
echo "systemd-timesyncd service is active (running)."
|
||||
else
|
||||
echo "systemd-timesyncd service is not running."
|
||||
fi
|
||||
else
|
||||
echo "systemctl is not available. Cannot determine NTP status this way."
|
||||
fi
|
||||
|
||||
echo
|
||||
}
|
||||
|
||||
set_time() {
|
||||
local new_time="$1"
|
||||
|
||||
if [ -z "$new_time" ]; then
|
||||
abort "Please specify a time, e.g. '2025-03-21 18:00:00'"
|
||||
fi
|
||||
|
||||
echo "Setting system time to: $new_time"
|
||||
# Using 'date' to set system time.
|
||||
# Format can be e.g. "YYYY-MM-DD HH:MM:SS"
|
||||
# Usually you need root privileges for this:
|
||||
if ! date -s "$new_time"; then
|
||||
abort "Failed to set system time. Check your permissions?"
|
||||
fi
|
||||
|
||||
# Also sync to hardware clock:
|
||||
if command -v hwclock &>/dev/null; then
|
||||
hwclock --systohc || echo "Warning: couldn't sync with HW clock."
|
||||
fi
|
||||
}
|
||||
|
||||
list_timezones() {
|
||||
local zoneinfo_dir="/usr/share/zoneinfo"
|
||||
if [ ! -d "$zoneinfo_dir" ]; then
|
||||
abort "Cannot find $zoneinfo_dir directory."
|
||||
fi
|
||||
|
||||
echo "List of available time zones (under $zoneinfo_dir):"
|
||||
# We filter out possible files that are not real zone data.
|
||||
# On some systems, zone data might be in subdirectories (Region/City).
|
||||
find "$zoneinfo_dir" -type f | sed "s|^$zoneinfo_dir/||"
|
||||
}
|
||||
|
||||
set_timezone() {
|
||||
local tz="$1"
|
||||
|
||||
if [ -z "$tz" ]; then
|
||||
abort "Please specify a time zone, e.g. 'America/New_York'"
|
||||
fi
|
||||
|
||||
local zoneinfo_file="/usr/share/zoneinfo/$tz"
|
||||
if [ ! -f "$zoneinfo_file" ]; then
|
||||
abort "Time zone '$tz' not found under /usr/share/zoneinfo"
|
||||
fi
|
||||
|
||||
echo "Linking /etc/localtime to $zoneinfo_file"
|
||||
ln -sf "$zoneinfo_file" /etc/localtime || abort "Failed to link /etc/localtime"
|
||||
|
||||
# Some distros use /etc/timezone to store the name of the current timezone
|
||||
if [ -w /etc/timezone ]; then
|
||||
echo "$tz" >/etc/timezone
|
||||
fi
|
||||
|
||||
echo "Time zone changed to $tz."
|
||||
}
|
||||
|
||||
set_local_rtc() {
|
||||
local is_local="$1"
|
||||
|
||||
if [ -z "$is_local" ]; then
|
||||
abort "Please specify 0 or 1. Example: set-local-rtc 1"
|
||||
fi
|
||||
|
||||
# We update /etc/adjtime accordingly:
|
||||
# - If local time: last line should be LOCAL
|
||||
# - If UTC: last line should be UTC
|
||||
# We also update the hardware clock accordingly.
|
||||
if [ "$is_local" = "1" ]; then
|
||||
echo "Configuring RTC to use local time."
|
||||
# "hwclock --localtime" is fairly ambiguous (it sets hardware clock from system time).
|
||||
# We use the below approach:
|
||||
hwclock --systohc --localtime || echo "Warning: couldn't set HW clock localtime."
|
||||
# Overwrite /etc/adjtime fully:
|
||||
cat <<EOF >/etc/adjtime
|
||||
0.0 0 0.0
|
||||
0
|
||||
LOCAL
|
||||
EOF
|
||||
else
|
||||
echo "Configuring RTC to use UTC."
|
||||
hwclock --systohc --utc || echo "Warning: couldn't set HW clock to UTC."
|
||||
cat <<EOF >/etc/adjtime
|
||||
0.0 0 0.0
|
||||
0
|
||||
UTC
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
set_ntp() {
|
||||
local enable_ntp="$1"
|
||||
if ! command -v systemctl &>/dev/null; then
|
||||
echo "systemctl not available. Cannot manage systemd-timesyncd. Exiting."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# systemd-timesyncd is the typical built-in NTP client for systemd-based systems
|
||||
case "$enable_ntp" in
|
||||
true | True | 1 | on)
|
||||
echo "Enabling and starting systemd-timesyncd..."
|
||||
systemctl enable systemd-timesyncd || echo "Failed to enable timesyncd."
|
||||
systemctl start systemd-timesyncd || echo "Failed to start timesyncd."
|
||||
;;
|
||||
false | False | 0 | off)
|
||||
echo "Disabling and stopping systemd-timesyncd..."
|
||||
systemctl disable systemd-timesyncd || echo "Failed to disable timesyncd."
|
||||
systemctl stop systemd-timesyncd || echo "Failed to stop timesyncd."
|
||||
;;
|
||||
*)
|
||||
abort "Unknown argument '$enable_ntp'. Use 'true' or 'false'."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Main
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
main() {
|
||||
local cmd="$1"
|
||||
shift || true
|
||||
|
||||
case "$cmd" in
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
set-time)
|
||||
set_time "$@"
|
||||
;;
|
||||
set-timezone)
|
||||
set_timezone "$@"
|
||||
;;
|
||||
list-timezones)
|
||||
list_timezones
|
||||
;;
|
||||
set-local-rtc)
|
||||
set_local_rtc "$@"
|
||||
;;
|
||||
set-ntp)
|
||||
set_ntp "$@"
|
||||
;;
|
||||
"" | help | --help | -h)
|
||||
print_usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $cmd"
|
||||
print_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
if [ $# -lt 1 ]; then
|
||||
print_usage
|
||||
exit 0
|
||||
fi
|
||||
main "$@"
|
||||
fi
|
||||
BIN
birdnet-pi-zach/stats.png
Normal file
BIN
birdnet-pi-zach/stats.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
9
birdnet-pi-zach/updater.json
Normal file
9
birdnet-pi-zach/updater.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"last_update": "10-07-2026",
|
||||
"paused": true,
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "birdnet-pi-zach",
|
||||
"source": "github",
|
||||
"upstream_repo": "zach7036/BirdNET-Pi-Enhanced-Version",
|
||||
"upstream_version": ""
|
||||
}
|
||||
Reference in New Issue
Block a user