use symlinks

This commit is contained in:
Alexandre
2025-03-21 07:39:30 +01:00
parent c941a85210
commit 57798c92b5
19 changed files with 850 additions and 465 deletions

View File

@@ -1,105 +1,261 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# Adapted from https://github.com/mcguirepr89/BirdNET-Pi/issues/393#issuecomment-1166445710
# Improved BirdNET-Pi Monitoring Script with Recovery Alerts and Condensed Logs
HOME="/home/pi"
# Define logging functions
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_info() { echo -e "\033[34m$1\033[0m"; }
echo "$(log_green "Starting service: throttlerecording")"
touch "$HOME/BirdSongs/StreamData/analyzing_now.txt"
########################################
# 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
# Set constants
srv="birdnet_recording"
srv2="birdnet_analysis"
ingest_dir="$RECS_DIR/StreamData"
counter=10
# Ensure directories and permissions
mkdir -p "$ingest_dir"
chown -R pi:pi "$ingest_dir"
chmod -R 755 "$ingest_dir"
########################################
# Wait 5 minutes for system stabilization
########################################
sleep 5m
# Function to send notifications using Apprise
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 notification=""
local stopped_service="<br><b>Stopped services:</b> "
local issue_message="$1"
local current_time
current_time=$(date +%s)
# Check for stopped services
services=(birdnet_analysis chart_viewer spectrogram_viewer icecast2 birdnet_recording birdnet_log birdnet_stats)
for service in "${services[@]}"; do
if [[ "$(systemctl is-active "$service")" == "inactive" ]]; then
# 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
# Build notification message
local notification="<b>Issue:</b> $issue_message"
notification+="$stopped_service"
notification+="<br><b>Additional information</b>: "
notification+="<br><b>Since:</b> ${LASTCHECK:-unknown}"
notification+="<br><b>System:</b> ${SITE_NAME:-$(hostname)}"
notification+="<br>Available disk space: $(df -h "$HOME/BirdSongs" | awk 'NR==2 {print $4}')"
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>"
# Send notification
TITLE="BirdNET-Analyzer stopped"
"$HOME/BirdNET-Pi/birdnet/bin/apprise" -vv -t "$TITLE" -b "$notification" --input-format=html --config="$HOME/BirdNET-Pi/apprise.txt"
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
}
# Main loop
while true; do
sleep 61
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."
# Restart analysis if clogged
if ((counter <= 0)); then
current_file="$(cat "$ingest_dir/analyzing_now.txt")"
if [[ "$current_file" == "$analyzing_now" ]]; then
log_yellow "$(date) WARNING: no change in analyzing_now for 10 iterations, restarting services"
"$HOME/BirdNET-Pi/scripts/restart_services.sh"
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
counter=10
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
# Check recorder state and queue length
wav_count=$(find "$ingest_dir" -maxdepth 1 -name '*.wav' | wc -l)
service_state=$(systemctl is-active "$srv")
analysis_state=$(systemctl is-active "$srv2")
log_green "$(date) INFO: $wav_count wav files waiting in $ingest_dir, $srv state is $service_state, $srv2 state is $analysis_state"
# Pause recorder if queue is too large
if ((wav_count > 50)); then
log_red "$(date) WARNING: Too many files in queue, pausing $srv and restarting $srv2"
sudo systemctl stop "$srv"
sudo systemctl restart "$srv2"
sleep 30
elif ((wav_count > 30)); then
log_red "$(date) WARNING: Too many files in queue, restarting $srv2"
sudo systemctl restart "$srv2"
sleep 30
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 service states
for service in "$srv" "$srv2"; do
state_var="${service}_state"
if [[ "${state_var:-}" != "active" ]]; then
log_yellow "$(date) INFO: Restarting $service service"
sudo systemctl restart "$service"
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
# Send alert if needed
if ((wav_count > 30)) && [[ -s "$HOME/BirdNET-Pi/apprise.txt" ]]; then
apprisealert
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
}
((counter--))
########################################
# Main Monitoring Loop
########################################
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
# 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

View File

@@ -1,6 +1,16 @@
#!/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."
@@ -18,16 +28,22 @@ if [ -d "$HOME"/BirdSongs/StreamData ]; then
# Wait for both services to stop
wait
# Check if there are files in StreamData and move them to /data/StreamData
# Create the destination directory
mkdir -p /data/StreamData
if [ "$(ls -A "$HOME"/BirdSongs/StreamData)" ]; then
if mv -v "$HOME"/BirdSongs/StreamData/* /data/StreamData/; then
bashio::log.info "Files successfully moved to /data/StreamData."
# 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" /data/StreamData/; then
bashio::log.info "Moved valid WAV file: $(basename "$file")"
else
bashio::log.error "Failed to move: $(basename "$file")"
fi
else
bashio::log.error "Failed to move files to /data/StreamData."
exit 1
bashio::log.warning "Skipping invalid or large file: $(basename "$file")"
fi
fi
done
bashio::log.info "... files safe, allowing container to stop."
else

View File

@@ -2,6 +2,17 @@
# 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 #
###############
@@ -18,8 +29,6 @@ for file in "${DEFAULT_FILES[@]}"; do
done
touch /config/include_species_list.txt # Ensure this is always created
touch "$HOME/BirdNET-Pi/scripts/common.php"
# Set BirdSongs folder location from configuration if specified
BIRDSONGS_FOLDER="/config/BirdSongs"
if bashio::config.has_value "BIRDSONGS_FOLDER"; then
@@ -53,6 +62,14 @@ 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"
@@ -65,12 +82,6 @@ elif [ "$(stat -c%s /config/birds.db)" -lt 10240 ]; then
cp "$HOME/BirdNET-Pi/scripts/birds.db" /config/
fi
# Backup default birdnet.conf for sanity check
if [ ! -f "$HOME/BirdNET-Pi/birdnet.conf" ]; then
ln -s /etc/birdnet/birdnet.conf "$HOME/BirdNET-Pi/birdnet.conf"
fi
cp "$HOME/BirdNET-Pi/birdnet.conf" "$HOME/BirdNET-Pi/birdnet.bak"
# Symlink configuration files
echo "... creating symlinks for configuration files"
CONFIG_FILES=("$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")
@@ -84,12 +95,6 @@ for file in "${CONFIG_FILES[@]}"; do
sudo -u pi ln -fs "/config/$filename" "/etc/birdnet/$filename"
done
# thisrun
cp /config/birdnet.conf "$HOME/BirdNET-Pi/scripts/thisrun.txt"
cp /config/birdnet.conf "$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 BirdSongs folders
for folder in By_Date Charts; do
echo "... creating symlink for $BIRDSONGS_FOLDER/$folder"
@@ -102,9 +107,9 @@ echo "... checking and setting permissions"
chmod -R 755 /config/*
chmod 777 /config
# Create Matplotlib configuration directory
# Create folder for matplotlib
echo "... setting up Matplotlabdir"
MPLCONFIGDIR="${MPLCONFIGDIR:-$HOME/.config/matplotlib}"
mkdir -p "$MPLCONFIGDIR"
chown pi:pi "$MPLCONFIGDIR"
chmod 777 "$MPLCONFIGDIR"
mkdir -p "$HOME"/.cache/matplotlib
chown -R "pi:pi" "$HOME"/.cache/matplotlib
chmod 777 "$HOME"/.cache/matplotlib

View File

@@ -2,22 +2,38 @@
# shellcheck shell=bash
set -e
if [ -d /data/StreamData ]; then
bashio::log.fatal "Container was stopped while files were still being analyzed."
##################
# ALLOW RESTARTS #
##################
# Check if there are .wav files in /data/StreamData
if find /data/StreamData -type f -name "*.wav" | grep -q .; then
bashio::log.warning "Restoring .wav files from /data/StreamData to $HOME/BirdSongs/StreamData."
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 /data/StreamData -type f -name "*.wav" | wc -l)
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 /data/StreamData/*.wav "$HOME"/BirdSongs/StreamData/
mv -v /config/TemporaryFiles/*.wav "$HOME"/BirdSongs/StreamData/
rm -r /config/TemporaryFiles
# Update permissions only if files were moved successfully
if [ "$file_count" -gt 0 ]; then
@@ -30,7 +46,7 @@ if [ -d /data/StreamData ]; then
fi
# Clean up the source folder if it is empty
if [ -z "$(ls -A /data/StreamData)" ]; then
rm -r /data/StreamData
if [ -z "$(ls -A /config/TemporaryFiles)" ]; then
rm -r /config/TemporaryFiles
fi
fi

View File

@@ -2,6 +2,17 @@
# 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 #
######################
@@ -48,17 +59,17 @@ fi
bashio::log.info "Performing potential updates"
# Adapt update_birdnet_snippets
sed -i "/USER=/a USER=\"$USER\"" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
sed -i "/HOME=/a HOME=\"$HOME\"" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
sed -i "/chown/d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
sed -i "/chmod/d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
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 "/restart_services/d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
sed -i "/set -x/d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
sed -i "s|systemctl list-unit-files|false \&\& echo|g" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
sed -i "/systemctl /d" "$HOME"/BirdNET-Pi/scripts/update_birdnet_snippets.sh
sed -i "/find /d" "$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

View 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 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 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

View File

@@ -0,0 +1,66 @@
#!/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
################
# ADD FEATURES #
################
bashio::log.info "Adding optional features"
# Enable the Processed folder
#############################
if bashio::config.true "PROCESSED_FOLDER_ENABLED" && ! grep -q "processed_size" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py; then
echo "... Enabling the Processed folder : the last 15 wav files will be stored there"
# Adapt config.php
sed -i "/GET\[\"info_site\"\]/a\ \$processed_size = \$_GET\[\"processed_size\"\];" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\$contents = file_get_contents/a\ \$contents = preg_replace\(\"/PROCESSED_SIZE=\.\*/\", \"PROCESSED_SIZE=\$processed_size\", \$contents\);" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i <table class=\"settingstable\"><tr><td>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i <h2>Processed folder management </h2>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i <label for=\"processed_size\">Amount of files to keep after analysis :</label>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i <input name=\"processed_size\" type=\"number\" style=\"width:6em;\" max=\"90\" min=\"0\" step=\"1\" value=\"<\?php print(\$config\['PROCESSED_SIZE'\]);?>\"/>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i </td></tr><tr><td>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i Processed is the directory where the formerly 'Analyzed' files are moved after extractions, mostly for troubleshooting purposes.<br>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i This value defines the maximum amount of files that are kept before replacement with new files.<br>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i </td></tr></table>" "$HOME"/BirdNET-Pi/scripts/config.php
sed -i "/\"success\"/i\ <br>" "$HOME"/BirdNET-Pi/scripts/config.php
# Adapt birdnet_analysis.py - move_to_processed
sed -i "/log.info('handle_reporting_queue done')/a\ os.remove(files.pop(0))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ while len(files) > processed_size:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ files.sort(key=os.path.getmtime)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ files = glob.glob(os.path.join(processed_dir, '*'))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ os.rename(file_name, os.path.join(processed_dir, os.path.basename(file_name)))" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ processed_dir = os.path.join(get_settings()['RECS_DIR'], 'Processed')" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\def move_to_processed(file_name, processed_size):" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ " "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
# Adapt birdnet_analysis.py - get_processed_size
sed -i "/log.info('handle_reporting_queue done')/a\ return 0" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ except (ValueError, TypeError):" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ return processed_size if isinstance(processed_size, int) else 0" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ processed_size = get_settings().getint('PROCESSED_SIZE')" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ try:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\def get_processed_size():" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/log.info('handle_reporting_queue done')/a\ " "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
# Modify calls
sed -i "/from subprocess import CalledProcessError/a\import glob" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/from subprocess import CalledProcessError/a\import time" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
# Modify main code
sed -i "/os.remove(file.file_name)/i\ processed_size = get_processed_size()" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/os.remove(file.file_name)/i\ if processed_size > 0:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/os.remove(file.file_name)/i\ move_to_processed(file.file_name, processed_size)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/os.remove(file.file_name)/i\ else:" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
sed -i "/os.remove(file.file_name)/c\ os.remove(file.file_name)" "$HOME"/BirdNET-Pi/scripts/birdnet_analysis.py
fi || true

View File

@@ -1,7 +1,18 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
# 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 #
################
@@ -14,74 +25,115 @@ sed -i '/>System Controls/d' "$HOME/BirdNET-Pi/homepage/views.php"
# Remove Ram drive option from webui
echo "... removing Ram drive from webui as it is handled from HA"
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"
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
# Correct services to start as user pi
echo "... updating services to start as user pi"
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 "b*.service" -print)
if ! grep -q "/usr/bin/sudo" "$HOME/BirdNET-Pi/templates/birdnet_log.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 "birdnet*.service" -print)
fi
# Send services log to container logs
echo "... redirecting services logs to container logs"
while IFS= read -r file; do
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 "b*.service" -print)
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)
# Avoid preselection in include and exclude lists
echo "... disabling preselecting options in include and exclude lists"
sed -i "s|option selected|option disabled|g" "$HOME/BirdNET-Pi/scripts/include_list.php"
sed -i "s|option selected|option disabled|g" "$HOME/BirdNET-Pi/scripts/exclude_list.php"
# Preencode API key
if ! 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
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"
# Caddyfile modifications
echo "... modifying Caddyfile configurations"
caddy fmt --overwrite /etc/caddy/Caddyfile
#Change port to leave 80 free for certificate requests
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
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
# Correct webui paths
echo "... correcting webui paths"
sed -i "s|/stats|/stats/|g" "$HOME/BirdNET-Pi/homepage/views.php"
sed -i "s|/log|/log/|g" "$HOME/BirdNET-Pi/homepage/views.php"
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
# Check if port 80 is correctly configured
if [ -n "$(bashio::addon.port 80)" ] && [ "$(bashio::addon.port 80)" != 80 ]; then
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
# Correct systemctl path
echo "... updating systemctl path"
mv /helpers/systemctl3.py /bin/systemctl
chmod a+x /bin/systemctl
#echo "... updating systemctl path"
#if [[ -f /helpers/systemctl3.py ]]; then
# mv /helpers/systemctl3.py /bin/systemctl
# chmod a+x /bin/systemctl
#fi
# Improve streamlit cache
#echo "... add streamlit cache"
#sed -i "/def get_data/i \\@st\.cache_resource\(\)" "$HOME/BirdNET-Pi/scripts/plotly_streamlit.py"
# 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
echo ".. add highpass and lowpass to sox extracts"
sed -i "s|f'={stop}']|f'={stop}', 'highpass', '250', 'lowpass', '15000']|g" "$HOME/BirdNET-Pi/scripts/utils/reporting.py"
sed -i '/sox.*-V1/s/spectrogram/highpass 250 spectrogram/' "$HOME/BirdNET-Pi/scripts/spectrogram.sh"
# Correct timedatectl path
echo "updating timedatectl path"
mv /helpers/timedatectl /usr/bin/timedatectl
chown pi:pi /usr/bin/timedatectl
chmod a+x /usr/bin/timedatectl
if [[ -f /helpers/timedatectl ]]; then
mv /helpers/timedatectl /usr/bin/timedatectl
chown pi:pi /usr/bin/timedatectl
chmod a+x /usr/bin/timedatectl
fi
# Correct timezone showing in config.php
# shellcheck disable=SC2016
echo "... updating timezone in config.php"
sed -i -e '/<option disabled selected>/s/selected//' \
-e '/\$current_timezone = trim(shell_exec("timedatectl show --value --property=Timezone"));/d' \
-e "/\$date = new DateTime('now');/i \$current_timezone = trim(shell_exec(\"timedatectl show --value --property=Timezone\"));" \
-e "/\$date = new DateTime('now');/i date_default_timezone_set(\$current_timezone);" "$HOME/BirdNET-Pi/scripts/config.php"
# Use only first user
echo "... correcting for multiple users"
for file in $(grep -rl "/1000/{print" "$HOME"/BirdNET-Pi/scripts); do
sed -i "s|'/1000/{print \$1}'|'/1000/{print \$1; exit}'|" "$file"
sed -i "s|'/1000/{print \$6}'|'/1000/{print \$6; exit}'|" "$file"
done
# Correct language labels according to birdnet.conf
echo "... adapting labels according to birdnet.conf"
if export "$(grep "^DATABASE_LANG" /config/birdnet.conf)"; then
bashio::log.info "Setting language to ${DATABASE_LANG:-en}"
"$HOME/BirdNET-Pi/scripts/install_language_label_nm.sh" -l "${DATABASE_LANG:-}" &>/dev/null || bashio::log.warning "Failed to update language labels"
else
bashio::log.warning "DATABASE_LANG not found in configuration. Using default labels."
fi

View File

@@ -2,6 +2,17 @@
# 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 #
#################
@@ -12,8 +23,9 @@ ingress_interface=$(bashio::addon.ip_address)
ingress_entry=$(bashio::addon.ingress_entry)
# Quits if ingress is not active
if [ -z "$ingress_entry" ]; then
bashio::log.warning "Ingress entry is not set, exiting configuration."
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
@@ -31,14 +43,20 @@ else
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
for php_file in config.php play.php advanced.php overview.php; do
sed -i "s|if (\!isset(\$_SERVER\['PHP_AUTH_USER'\])) {|if (\!isset(\$_SERVER\['PHP_AUTH_USER'\]) \&\& strpos(\$_SERVER\['HTTP_REFERER'\], '/api/hassio_ingress') == false) {|g" "$HOME/BirdNET-Pi/scripts/$php_file"
sed -i "s+if(\$submittedpwd == \$caddypwd \&\& \$submitteduser == 'birdnet')+if((\$submittedpwd == \$caddypwd \&\& \$submitteduser == 'birdnet') || (strpos(\$_SERVER['HTTP_REFERER'], '/api/hassio_ingress') !== false \&\& strpos(\$_SERVER['HTTP_REFERER'], trim(file_get_contents('/ingress_url'))) !== false)+g" "$HOME/BirdNET-Pi/scripts/$php_file"
done
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.error "PHP file not found: $php_file"
exit 1
fi
echo "... adapting Caddyfile for ingress"
chmod +x /helpers/caddy_ingress.sh

View File

@@ -2,6 +2,17 @@
# 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 #
###############

View 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 777 /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

View File

@@ -1,18 +1,32 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
set -e
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"
echo "pi:$(bashio::config "pi_password")" | chpasswd
if bashio::config.has_value "pi_password"; then
echo "pi:$(bashio::config "pi_password")" | chpasswd
fi
bashio::log.info "Password set successfully for user pi."
bashio::log.info "Setting timezone :"
# 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
@@ -38,7 +52,11 @@ else
else
bashio::log.fatal "Couldn't set automatic timezone! Please set a manual one from the options."
fi
fi
fi || 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"
@@ -53,11 +71,12 @@ 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
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
bashio::log.info "Setup complete."
# Start
bashio::log.info "✅ Setup complete."

View File

@@ -1,4 +1,4 @@
server {
server {
listen %%interface%%:%%port%% default_server;
include /etc/nginx/includes/server_params.conf;
include /etc/nginx/includes/proxy_params.conf;
@@ -6,21 +6,6 @@
proxy_buffering off;
auth_basic_user_file /home/pi/.htpasswd;
location /log {
# Proxy pass
proxy_pass http://localhost:8082;
}
location /stats {
# Proxy pass
proxy_pass http://localhost:8082;
}
location /terminal {
# Proxy pass
proxy_pass http://localhost:8082;
}
location / {
# Proxy pass
proxy_pass http://localhost:8082;
@@ -30,6 +15,12 @@
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;
@@ -37,11 +28,14 @@
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%%/;
}
}

View File

@@ -51,8 +51,8 @@ def get_bird_code(scientific_name):
def automatic_mqtt_publish(file, detection, path):
bird = {}
bird['Date'] = file.date
bird['Time'] = file.time
bird['Date'] = detection.date
bird['Time'] = detection.time
bird['ScientificName'] = detection.scientific_name.replace('_', ' ')
bird['CommonName'] = detection.common_name
bird['Confidence'] = detection.confidence
@@ -86,7 +86,7 @@ def automatic_mqtt_publish(file, detection, path):
json_bird = json.dumps(bird)
mqttc.reconnect()
mqttc.publish(mqtt_topic, json_bird, 1)
mqttc.publish(mqtt_topic, json_bird, 1)
log.info("Posted to MQTT: ok")
mqttc = mqtt.Client('birdnet_mqtt')

View File

@@ -1,116 +0,0 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
</style>
<p><strong>This tool will allow to convert on-the-fly species to compensate for model errors. It SHOULD NOT BE USED except if you know what you are doing, instead the model errors should be reported to the owner. However, it is still convenient for systematic biases that are confirmed through careful listening of samples, while waiting for the models to be updated.</strong></p>
<div class="customlabels column1">
<form action="" method="GET" id="add">
<input type="hidden" id="species" name="species">
<h3>Specie to convert from :</h3>
<!-- Input box to filter options in the first table -->
<input type="text" id="species1Search" onkeyup="filterOptions('species1')" placeholder="Search for species...">
<select name="species1" id="species1" size="25">
<?php
error_reporting(E_ALL);
ini_set('display_errors',1);
$filename = './scripts/labels.txt';
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
foreach($eachline as $lines){echo
"<option value=\"".$lines."\">$lines</option>";}
?>
</select>
<br><br> <!-- Added a space between the two tables -->
<h3>Specie to convert to :</h3>
<!-- Input box to filter options in the second table -->
<input type="text" id="species2Search" onkeyup="filterOptions('species2')" placeholder="Search for species...">
<select name="species2" id="species2" size="25">
<?php
foreach($eachline as $lines){echo
"<option value=\"".$lines."\">$lines</option>";}
?>
</select>
<input type="hidden" name="add" value="add">
</form>
<div class="customlabels smaller">
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
</div>
</div>
<div class="customlabels column2">
<table><td>
<button type="submit" name="view" value="Converted" form="add">>>ADD>></button>
<br><br>
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
</td></table>
</div>
<div class="customlabels column3" style="margin-top: 0;"> <!-- Removed the blank space above the table -->
<form action="" method="GET" id="del">
<h3>Converted Species List</h3>
<select name="species[]" id="value2" multiple size="25">
<?php
$filename = './scripts/convert_species_list.txt'; // Changed the file path
$eachline = file($filename, FILE_IGNORE_NEW_LINES);
foreach($eachline as $lines){
echo
"<option value=\"".$lines."\">$lines</option>";
}?>
</select>
<input type="hidden" name="del" value="del">
</form>
<div class="customlabels smaller">
<button type="submit" name="view" value="Converted" form="del">REMOVE</button>
</div>
</div>
<input type="hidden" id="hiddenSpecies" name="hiddenSpecies">
<script>
document.getElementById("add").addEventListener("submit", function(event) {
var speciesSelect1 = document.getElementById("species1");
var speciesSelect2 = document.getElementById("species2");
if (speciesSelect1.selectedIndex < 0 || speciesSelect2.selectedIndex < 0) {
alert("Please select a species from both lists.");
document.querySelector('.views').style.opacity = 1;
event.preventDefault();
} else {
var selectedSpecies1 = speciesSelect1.options[speciesSelect1.selectedIndex].value;
var selectedSpecies2 = speciesSelect2.options[speciesSelect2.selectedIndex].value;
document.getElementById("species").value = selectedSpecies1 + ";" + selectedSpecies2;
}
});
// Store the original list of options in a variable
var originalOptions = {};
// Function to filter options in a select element
function filterOptions(id) {
var input = document.getElementById(id + "Search");
var filter = input.value.toUpperCase();
var select = document.getElementById(id);
var options = select.getElementsByTagName("option");
// If the original list of options for this select element hasn't been stored yet, store it
if (!originalOptions[id]) {
originalOptions[id] = Array.from(options).map(option => option.value);
}
// Clear the select element
while (select.firstChild) {
select.removeChild(select.firstChild);
}
// Populate the select element with the filtered labels
originalOptions[id].forEach(label => {
if (label.toUpperCase().indexOf(filter) > -1) {
let option = document.createElement('option');
option.value = label;
option.text = label;
select.appendChild(option);
}
});
}
</script>

View File

@@ -0,0 +1,31 @@
#! /usr/bin/python3
import argparse
import os
import sys
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--unit', metavar='unit', type=str, required=True, help='Systemd unit to display')
parser.add_argument('-f', '--follow', default=False, action='store_true', help='Follows the log')
parser.add_argument('-n', '--lines', metavar='num', type=int, help='Num of lines to display')
parser.add_argument('--no-pager', default=False, action='store_true', help='Do not pipe through a pager')
parser.add_argument('--system', default=False, action='store_true', help='Show system units')
parser.add_argument('--user', default=False, action='store_true', help='Show user units')
parser.add_argument('--root', metavar='path', type=str, help='Use subdirectory path')
parser.add_argument('-x', default=False, action='store_true', help='Switch on verbose mode')
args = parser.parse_args()
systemctl_py = "systemctl3.py"
path = os.path.dirname(sys.argv[0])
systemctl = os.path.join(path, systemctl_py)
cmd = [ systemctl, "log", args.unit ] # drops the -u
if args.follow: cmd += [ "-f" ]
if args.lines: cmd += [ "-n", str(args.lines) ]
if args.no_pager: cmd += [ "--no-pager" ]
if args.system: cmd += [ "--system" ]
elif args.user: cmd += [ "--user" ]
if args.root: cmd += [ "--root", start(args.root) ]
if args.x: cmd += [ "-vvv" ]
os.execvp(cmd[0], cmd)

View File

@@ -21,8 +21,8 @@ import fnmatch
import re
from types import GeneratorType
__copyright__ = "(C) 2016-2024 Guido U. Draheim, licensed under the EUPL"
__version__ = "1.5.8066"
__copyright__ = "(C) 2016-2025 Guido U. Draheim, licensed under the EUPL"
__version__ = "1.5.9063"
# |
# |
@@ -561,9 +561,8 @@ def shutil_truncate(filename):
filedir = os.path.dirname(filename)
if not os.path.isdir(filedir):
os.makedirs(filedir)
f = open(filename, "w")
f.write("")
f.close()
with open(filename, "w") as f:
f.write("")
# http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
def pid_exists(pid):
@@ -615,9 +614,10 @@ def _pid_zombie(pid):
raise ValueError('invalid PID 0')
check = _proc_pid_status.format(**locals())
try:
for line in open(check):
if line.startswith("State:"):
return "Z" in line
with open(check) as f:
for line in f:
if line.startswith("State:"):
return "Z" in line
except IOError as e:
if e.errno != errno.ENOENT:
logg.error("%s (%s): %s", check, e.errno, e)
@@ -770,46 +770,49 @@ class SystemctlConfigParser(SystemctlConfData):
name, text = "", ""
if os.path.isfile(filename):
self._files.append(filename)
for orig_line in open(filename):
if nextline:
text += orig_line
if text.rstrip().endswith("\\") or text.rstrip().endswith("\\\n"):
text = text.rstrip() + "\n"
with open(filename) as f:
for orig_line in f:
if nextline:
text += orig_line
if text.rstrip().endswith("\\") or text.rstrip().endswith("\\\n"):
text = text.rstrip() + "\n"
else:
self.set(section, name, text)
nextline = False
continue
line = orig_line.strip()
if not line:
continue
if line.startswith("#"):
continue
if line.startswith(";"):
continue
if line.startswith(".include"):
logg.error("the '.include' syntax is deprecated. Use x.service.d/ drop-in files!")
includefile = re.sub(r'^\.include[ ]*', '', line).rstrip()
if not os.path.isfile(includefile):
raise Exception("tried to include file that doesn't exist: %s" % includefile)
self.read_sysd(includefile)
continue
if line.startswith("["):
x = line.find("]")
if x > 0:
section = line[1:x]
self.add_section(section)
continue
m = re.match(r"(\w+) *=(.*)", line)
if not m:
logg.warning("bad ini line: %s", line)
raise Exception("bad ini line")
name, text = m.group(1), m.group(2).strip()
if text.endswith("\\") or text.endswith("\\\n"):
nextline = True
text = text + "\n"
else:
self.set(section, name, text)
nextline = False
continue
line = orig_line.strip()
if not line:
continue
if line.startswith("#"):
continue
if line.startswith(";"):
continue
if line.startswith(".include"):
logg.error("the '.include' syntax is deprecated. Use x.service.d/ drop-in files!")
includefile = re.sub(r'^\.include[ ]*', '', line).rstrip()
if not os.path.isfile(includefile):
raise Exception("tried to include file that doesn't exist: %s" % includefile)
self.read_sysd(includefile)
continue
if line.startswith("["):
x = line.find("]")
if x > 0:
section = line[1:x]
self.add_section(section)
continue
m = re.match(r"(\w+) *=(.*)", line)
if not m:
logg.warning("bad ini line: %s", line)
raise Exception("bad ini line")
name, text = m.group(1), m.group(2).strip()
if text.endswith("\\") or text.endswith("\\\n"):
nextline = True
text = text + "\n"
else:
# hint: an empty line shall reset the value-list
self.set(section, name, text and text or None)
# hint: an empty line shall reset the value-list
self.set(section, name, text and text or None)
if nextline:
self.set(section, name, text)
return self
def read_sysv(self, filename):
""" an LSB header is scanned and converted to (almost)
@@ -819,20 +822,21 @@ class SystemctlConfigParser(SystemctlConfData):
section = "GLOBAL"
if os.path.isfile(filename):
self._files.append(filename)
for orig_line in open(filename):
line = orig_line.strip()
if line.startswith("#"):
if " BEGIN INIT INFO" in line:
initinfo = True
section = "init.d"
if " END INIT INFO" in line:
initinfo = False
if initinfo:
m = re.match(r"\S+\s*(\w[\w_-]*):(.*)", line)
if m:
key, val = m.group(1), m.group(2).strip()
self.set(section, key, val)
continue
with open(filename) as f:
for orig_line in f:
line = orig_line.strip()
if line.startswith("#"):
if " BEGIN INIT INFO" in line:
initinfo = True
section = "init.d"
if " END INIT INFO" in line:
initinfo = False
if initinfo:
m = re.match(r"\S+\s*(\w[\w_-]*):(.*)", line)
if m:
key, val = m.group(1), m.group(2).strip()
self.set(section, key, val)
continue
self.systemd_sysv_generator(filename)
return self
def systemd_sysv_generator(self, filename):
@@ -962,8 +966,9 @@ class PresetFile:
return None
def read(self, filename):
self._files.append(filename)
for line in open(filename):
self._lines.append(line.strip())
with open(filename) as f:
for line in f:
self._lines.append(line.strip())
return self
def get_preset(self, unit):
for line in self._lines:
@@ -1834,10 +1839,11 @@ class Systemctl:
return default
try:
# some pid-files from applications contain multiple lines
for line in open(pid_file):
if line.strip():
pid = to_intN(line.strip())
break
with open(pid_file) as f:
for line in f:
if line.strip():
pid = to_intN(line.strip())
break
except Exception as e:
logg.warning("bad read of pid file '%s': %s", pid_file, e)
return pid
@@ -1953,15 +1959,16 @@ class Systemctl:
return status
try:
if DEBUG_STATUS: logg.debug("reading %s", status_file)
for line in open(status_file):
if line.strip():
m = re.match(r"(\w+)[:=](.*)", line)
if m:
key, value = m.group(1), m.group(2)
if key.strip():
status[key.strip()] = value.strip()
else: # pragma: no cover
logg.warning("ignored %s", line.strip())
with open(status_file) as f:
for line in f:
if line.strip():
m = re.match(r"(\w+)[:=](.*)", line)
if m:
key, value = m.group(1), m.group(2)
if key.strip():
status[key.strip()] = value.strip()
else: # pragma: no cover
logg.warning("ignored %s", line.strip())
except:
logg.warning("bad read of status file '%s'", status_file)
return status
@@ -2060,7 +2067,6 @@ class Systemctl:
assert isinstance(line, bytes)
if line.startswith(b"btime"):
system_btime = float(line.decode().split()[1])
f.closed
if DEBUG_BOOTTIME:
logg.debug(" BOOT 2. System btime secs: %.3f (%s)", system_btime, system_stat)
@@ -2113,22 +2119,23 @@ class Systemctl:
logg.debug("file does not exist: %s", real_file)
return
try:
for real_line in open(os_path(self._root, env_file)):
line = real_line.strip()
if not line or line.startswith("#"):
continue
m = re.match(r"(?:export +)?([\w_]+)[=]'([^']*)'", line)
if m:
yield m.group(1), m.group(2)
continue
m = re.match(r'(?:export +)?([\w_]+)[=]"([^"]*)"', line)
if m:
yield m.group(1), m.group(2)
continue
m = re.match(r'(?:export +)?([\w_]+)[=](.*)', line)
if m:
yield m.group(1), m.group(2)
continue
with open(os_path(self._root, env_file)) as f:
for real_line in f:
line = real_line.strip()
if not line or line.startswith("#"):
continue
m = re.match(r"(?:export +)?([\w_]+)[=]'([^']*)'", line)
if m:
yield m.group(1), m.group(2)
continue
m = re.match(r'(?:export +)?([\w_]+)[=]"([^"]*)"', line)
if m:
yield m.group(1), m.group(2)
continue
m = re.match(r'(?:export +)?([\w_]+)[=](.*)', line)
if m:
yield m.group(1), m.group(2)
continue
except Exception as e:
logg.info("while reading %s: %s", env_file, e)
def read_env_part(self, env_part): # -> generate[ (name, value) ]
@@ -5293,18 +5300,18 @@ class Systemctl:
logg.error(" %s: %s has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing.", unit, section)
errors += 101
if len(usedExecStart) > 1 and haveType != "oneshot":
logg.error(" %s: there may be only one %s ExecStart statement (unless for 'oneshot' services)."
+ "\n\t\t\tYou can use ExecStartPre / ExecStartPost to add additional commands.", unit, section)
logg.error(" %s: there may be only one %s ExecStart statement (unless for 'oneshot' services)." +
"\n\t\t\tYou can use ExecStartPre / ExecStartPost to add additional commands.", unit, section)
errors += 1
if len(usedExecStop) > 1 and haveType != "oneshot":
logg.info(" %s: there should be only one %s ExecStop statement (unless for 'oneshot' services)."
+ "\n\t\t\tYou can use ExecStopPost to add additional commands (also executed on failed Start)", unit, section)
logg.info(" %s: there should be only one %s ExecStop statement (unless for 'oneshot' services)." +
"\n\t\t\tYou can use ExecStopPost to add additional commands (also executed on failed Start)", unit, section)
if len(usedExecReload) > 1:
logg.info(" %s: there should be only one %s ExecReload statement."
+ "\n\t\t\tUse ' ; ' for multiple commands (ExecReloadPost or ExedReloadPre do not exist)", unit, section)
logg.info(" %s: there should be only one %s ExecReload statement." +
"\n\t\t\tUse ' ; ' for multiple commands (ExecReloadPost or ExedReloadPre do not exist)", unit, section)
if len(usedExecReload) > 0 and "/bin/kill " in usedExecReload[0]:
logg.warning(" %s: the use of /bin/kill is not recommended for %s ExecReload as it is asynchronous."
+ "\n\t\t\tThat means all the dependencies will perform the reload simultaneously / out of order.", unit, section)
logg.warning(" %s: the use of /bin/kill is not recommended for %s ExecReload as it is asynchronous." +
"\n\t\t\tThat means all the dependencies will perform the reload simultaneously / out of order.", unit, section)
if conf.getlist(Service, "ExecRestart", []): # pragma: no cover
logg.error(" %s: there no such thing as an %s ExecRestart (ignored)", unit, section)
if conf.getlist(Service, "ExecRestartPre", []): # pragma: no cover
@@ -5961,7 +5968,7 @@ class Systemctl:
interval = conf.get(Service, "StartLimitIntervalSec", strE(defaults)) # 10s
return time_to_seconds(interval, maximum)
def get_RestartSec(self, conf, maximum = None):
maximum = maximum or DefaultStartLimitIntervalSec
maximum = maximum or DefaultMaximumTimeout
delay = conf.get(Service, "RestartSec", strE(DefaultRestartSec))
return time_to_seconds(delay, maximum)
def restart_failed_units(self, units, maximum = None):
@@ -6186,11 +6193,12 @@ class Systemctl:
zombie = False
ppid = -1
try:
for line in open(proc_status):
m = re.match(r"State:\s*Z.*", line)
if m: zombie = True
m = re.match(r"PPid:\s*(\d+)", line)
if m: ppid = int(m.group(1))
with open(proc_status) as f:
for line in f:
m = re.match(r"State:\s*Z.*", line)
if m: zombie = True
m = re.match(r"PPid:\s*(\d+)", line)
if m: ppid = int(m.group(1))
except IOError as e:
logg.warning("%s : %s", proc_status, e)
continue
@@ -6263,13 +6271,14 @@ class Systemctl:
proc_status = _proc_pid_status.format(**locals())
if os.path.isfile(proc_status):
try:
for line in open(proc_status):
if line.startswith("PPid:"):
ppid_text = line[len("PPid:"):].strip()
try: ppid = int(ppid_text)
except: continue
if ppid in pidlist and pid not in pids:
pids += [pid]
with open(proc_status) as f:
for line in f:
if line.startswith("PPid:"):
ppid_text = line[len("PPid:"):].strip()
try: ppid = int(ppid_text)
except: continue
if ppid in pidlist and pid not in pids:
pids += [pid]
except IOError as e:
logg.warning("%s : %s", proc_status, e)
continue
@@ -6302,7 +6311,8 @@ class Systemctl:
if pid:
try:
cmdline = _proc_pid_cmdline.format(**locals())
cmd = open(cmdline).read().split("\0")
with open(cmdline) as f:
cmd = f.read().split("\0")
if DEBUG_KILLALL: logg.debug("cmdline %s", cmd)
found = None
cmd_exe = os.path.basename(cmd[0])
@@ -6333,33 +6343,33 @@ class Systemctl:
logg.debug("checking hosts sysconf for '::1 localhost'")
lines = []
sysconf_hosts = os_path(self._root, _etc_hosts)
for line in open(sysconf_hosts):
if "::1" in line:
newline = re.sub("\\slocalhost\\s", " ", line)
if line != newline:
logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip())
line = newline
lines.append(line)
f = open(sysconf_hosts, "w")
for line in lines:
f.write(line)
f.close()
with open(sysconf_hosts) as f:
for line in f:
if "::1" in line:
newline = re.sub("\\slocalhost\\s", " ", line)
if line != newline:
logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip())
line = newline
lines.append(line)
with open(sysconf_hosts, "w") as f:
for line in lines:
f.write(line)
def force_ipv6(self, *args):
""" only ipv4 localhost in /etc/hosts """
logg.debug("checking hosts sysconf for '127.0.0.1 localhost'")
lines = []
sysconf_hosts = os_path(self._root, _etc_hosts)
for line in open(sysconf_hosts):
if "127.0.0.1" in line:
newline = re.sub("\\slocalhost\\s", " ", line)
if line != newline:
logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip())
line = newline
lines.append(line)
f = open(sysconf_hosts, "w")
for line in lines:
f.write(line)
f.close()
with open(sysconf_hosts) as f:
for line in f:
if "127.0.0.1" in line:
newline = re.sub("\\slocalhost\\s", " ", line)
if line != newline:
logg.info("%s: '%s' => '%s'", _etc_hosts, line.rstrip(), newline.rstrip())
line = newline
lines.append(line)
with open(sysconf_hosts, "w") as f:
for line in lines:
f.write(line)
def help_modules(self, *args):
"""[command] -- show this help
"""
@@ -6523,7 +6533,12 @@ def print_str_dict_dict(result):
logg.log(HINT, "EXEC END %i items", shown)
logg.debug(" END %s", result)
def run(command, *modules):
def runcommand(command, *modules):
systemctl = Systemctl()
if FORCE_IPV4:
systemctl.force_ipv4()
elif FORCE_IPV6:
systemctl.force_ipv6()
exitcode = 0
if command in ["help"]:
print_str_list(systemctl.help_modules(*modules))
@@ -6770,6 +6785,8 @@ if __name__ == "__main__":
_only_type = opt.only_type
_only_property = opt.only_property
_only_what = opt.only_what
FORCE_IPV4 = opt.ipv4
FORCE_IPV6 = opt.ipv6
# being PID 1 (or 0) in a container will imply --init
_pid = os.getpid()
_init = opt.init or _pid in [1, 0]
@@ -6829,7 +6846,6 @@ if __name__ == "__main__":
#
print_begin(sys.argv, args)
#
systemctl = Systemctl()
if opt.version:
args = ["version"]
if not args:
@@ -6844,8 +6860,4 @@ if __name__ == "__main__":
modules.remove("service")
except ValueError:
pass
if opt.ipv4:
systemctl.force_ipv4()
elif opt.ipv6:
systemctl.force_ipv6()
sys.exit(run(command, *modules))
sys.exit(runcommand(command, *modules))

View File

@@ -75,7 +75,7 @@ show_time_details() {
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"

View File

@@ -1,27 +0,0 @@
if($_GET['view'] == "Converted"){
ensure_authenticated();
if(isset($_GET['species']) && isset($_GET['add'])){
$file = './scripts/convert_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $str);
file_put_contents("$file", "$str");
// Write $_GET['species'] to the file
file_put_contents("./scripts/convert_species_list.txt", htmlspecialchars_decode($_GET['species'], ENT_QUOTES)."\n", FILE_APPEND);
} elseif (isset($_GET['species']) && isset($_GET['del'])){
$file = './scripts/convert_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
foreach($_GET['species'] as $selectedOption) {
$content = file_get_contents("./scripts/convert_species_list.txt");
$newcontent = str_replace($selectedOption, "", "$content");
$newcontent = str_replace(htmlspecialchars_decode($selectedOption, ENT_QUOTES), "", "$content");
file_put_contents("./scripts/convert_species_list.txt", "$newcontent");
}
$file = './scripts/convert_species_list.txt';
$str = file_get_contents("$file");
$str = preg_replace('/^\h*\v+/m', '', $str);
file_put_contents("$file", "$str");
}
include('./scripts/convert_list.php');
}