mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-01-11 18:31:02 +01:00
use symlinks
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
84
battybirdnet-pi/rootfs/etc/cont-init.d/33-mqtt.sh
Normal file
84
battybirdnet-pi/rootfs/etc/cont-init.d/33-mqtt.sh
Normal 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
|
||||
66
battybirdnet-pi/rootfs/etc/cont-init.d/71-newfeatures.sh
Normal file
66
battybirdnet-pi/rootfs/etc/cont-init.d/71-newfeatures.sh
Normal 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 #
|
||||
###############
|
||||
|
||||
37
battybirdnet-pi/rootfs/etc/cont-init.d/98-oldcpu.sh
Normal file
37
battybirdnet-pi/rootfs/etc/cont-init.d/98-oldcpu.sh
Normal 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
|
||||
@@ -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."
|
||||
|
||||
@@ -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%%/;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
31
battybirdnet-pi/rootfs/helpers/journalctl3.py
Normal file
31
battybirdnet-pi/rootfs/helpers/journalctl3.py
Normal 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)
|
||||
@@ -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))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
Reference in New Issue
Block a user