I would like to know if anyone has successfully been able to run write_node_callsigns.sh with ASL3. I am legally blind and have been using this feature on one of my HamVoip nodes. Many thanks, Steve WB4IZC
#!/bin/bash
# N5LSN
# Make app_rpt telemetry use ASL callsigns instead of node numbers
# Intended for use on ASL3
# Directories and files
SRCDIR="/var/log/asterisk"
DESTDIR="/usr/share/asterisk/sounds/en/rpt/nodenames"
RPTSOUNDS="/usr/share/asterisk/sounds/en/rpt"
LETTERS="/usr/share/asterisk/sounds/en/letters"
NUMBERS="/usr/share/asterisk/sounds/en/digits"
PREV_DB="/tmp/previous_astdb.txt"
# Start time tracking
start_time=$(date +%s%3N)
# Usage instructions
usage() {
cat << EOF
Usage: write_node_callsigns.sh options
OPTIONS:
-h Show this message
-a Process all nodes
-i Include node number with call
-n node Process a single node
-d path Specify destination directory (default: /usr/share/asterisk/sounds/en/rpt/nodenames)
-v Verbose output
-f Force run without user confirmation
Examples:
./write_node_callsigns.sh -a # Process all nodes
./write_node_callsigns.sh -n 40000 # Process single node 40000
./write_node_callsigns.sh -f # Force execution without confirmation
EOF
}
STRING=""
VERBOSE=""
INCNODE=""
FORCE_RUN=0
MAX_PREVIEW=10
# Create directories if missing
ensure_directory_exists() {
if [ ! -d "$1" ]; then
echo "Creating directory: $1"
mkdir -p "$1" || { echo "Failed to create directory $1"; exit 1; }
fi
}
# Find the correct audio file (.gsm or .ulaw)
find_audio_file() {
basepath=$1
if [ -f "${basepath}.gsm" ]; then
echo "${basepath}.gsm"
elif [ -f "${basepath}.ulaw" ]; then
echo "${basepath}.ulaw"
else
echo ""
fi
}
# Process each character in the callsign and form the audio filenames
make_call() {
local foo=${1,,}
STRING=""
for (( i=0; i<${#foo}; i++ )); do
local char=${foo:$i:1}
case $char in
[0-9]) FILENAME=$(find_audio_file "$NUMBERS/$char") ;;
"/") FILENAME=$(find_audio_file "$LETTERS/slash") ;;
"-") FILENAME=$(find_audio_file "$LETTERS/dash") ;;
[a-z]) FILENAME=$(find_audio_file "$LETTERS/$char") ;;
esac
if [ -n "$FILENAME" ]; then
STRING="$STRING $FILENAME"
else
echo "Error: Audio file for '$char' not found."
fi
done
}
# Handle .ulaw files with sox
process_file() {
local file=$1
if [[ "$file" == *.ulaw ]]; then
echo "-t raw -e u-law -r 8000 -c 1 $file"
else
echo "$file"
fi
}
# Concatenate the audio files into the final output and measure time
write_call() {
local output_file="$DESTDIR/$f1.gsm"
local start=$(date +%s%3N) # Get start time in milliseconds
# Generate processed file paths for sox
local processed_files=""
for file in $STRING; do
processed_files="$processed_files $(process_file $file)"
done
# Execute sox to concatenate files (always overwrite)
sox $processed_files $output_file
local end=$(date +%s%3N) # Get end time in milliseconds
local duration=$((end - start)) # Calculate processing time
# Output in the required format
echo "[$(printf "%04d" $duration)ms] - $f1 - $f2"
}
# Load previous database into associative array for fast lookup
declare -A previous_callsigns
load_previous_database() {
if [ -f "$PREV_DB" ]; then
while IFS='|' read -r node callsign _; do
# Ensure the node ID is valid (non-empty)
if [ -n "$node" ]; then
previous_callsigns["$node"]="$callsign"
fi
done < "$PREV_DB"
fi
}
# Compare current astdb.txt with previous_db.txt to find new or modified nodes
compare_databases() {
echo "Comparing databases..."
new_nodes=() # Array to store nodes that are new or changed
changes=() # Array to store changes for preview
declare -A latest_node_callsigns # To handle duplicate node numbers
while IFS='|' read -r f1 f2 _; do
# Skip lines starting with a semicolon or empty lines
[[ "$f1" =~ ^\; ]] || [ -z "$f1" ] && continue
# Handle duplicates by keeping the last occurrence of each node
latest_node_callsigns["$f1"]="$f2"
done < "$SRCDIR/astdb.txt"
# Now compare the latest callsigns with the previous database
for node in "${!latest_node_callsigns[@]}"; do
current_callsign="${latest_node_callsigns[$node]}"
old_callsign="${previous_callsigns[$node]}"
if [ -z "$old_callsign" ]; then
# New node
new_nodes+=("$node|$current_callsign")
changes+=("$node: NEW -> $current_callsign")
elif [ "$old_callsign" != "$current_callsign" ]; then
# Callsign has changed
new_nodes+=("$node|$current_callsign")
changes+=("$node: $old_callsign -> $current_callsign")
fi
done
}
# Prompt the user to confirm before proceeding, show the first 10 nodes with changes
confirm_processing() {
local node_count=$1
if [ $FORCE_RUN -eq 1 ]; then
return # Skip confirmation if forced to run
fi
echo "$node_count nodes need to be processed."
echo "Preview of changes:"
local i=0
for change in "${changes[@]}"; do
echo " $change"
((i++))
if [ $i -ge $MAX_PREVIEW ]; then
echo " ...and more."
break
fi
done
read -p "Continue? [y/n]: " response
case "$response" in
[yY][eE][sS]|[yY])
echo "Starting processing..."
;;
*)
echo "Aborting."
exit 0
;;
esac
}
# Update the previous_db.txt file with the current astdb.txt data
update_previous_db() {
cp "$SRCDIR/astdb.txt" "$PREV_DB"
}
# Format the total execution time into the appropriate unit (seconds, minutes, hours)
format_total_time() {
local total_time_ms=$1
if (( total_time_ms < 1000 )); then
# Less than 1 second: display in milliseconds
echo "${total_time_ms}ms"
elif (( total_time_ms < 60000 )); then
# Less than 1 minute: display in seconds
local total_time_sec=$(echo "scale=1; $total_time_ms / 1000" | bc)
echo "${total_time_sec}s"
elif (( total_time_ms < 3600000 )); then
# Less than 1 hour: display in minutes
local total_time_min=$(echo "scale=1; $total_time_ms / 60000" | bc)
echo "${total_time_min}min"
else
# More than 1 hour: display in hours
local total_time_hr=$(echo "scale=1; $total_time_ms / 3600000" | bc)
echo "${total_time_hr}h"
fi
}
# Main processing logic for new or modified nodes
process_nodes() {
# Load the previous database into memory for fast lookups
load_previous_database
# Compare current and previous databases
compare_databases
local node_count=${#new_nodes[@]}
if [ $node_count -eq 0 ]; then
echo "No new or changed nodes to process."
return
fi
# Confirm processing with user
confirm_processing "$node_count"
# Process new or modified nodes
for node_data in "${new_nodes[@]}"; do
IFS='|' read -r f1 f2 <<< "$node_data"
make_call "$f2"
if [ "$INCNODE" ]; then
STRING="$STRING $(find_audio_file "$RPTSOUNDS/node")"
make_call "$f1"
fi
write_call
done
# Update the previous database with the current data
update_previous_db
}
# Parse command-line options
while getopts "hail:vn:d:fv" OPTION; do
case $OPTION in
h) usage; exit 0 ;;
a) ;;
i) INCNODE=1 ;;
n) node=$OPTARG ;;
d) DESTDIR=$OPTARG ;;
f) FORCE_RUN=1 ;; # Force execution without confirmation
v) VERBOSE=1 ;;
?) usage; exit 1 ;;
esac
done
# Ensure necessary directories exist
ensure_directory_exists "$DESTDIR"
# Verify that the source file exists
if [ ! -f "$SRCDIR/astdb.txt" ]; then
echo "$SRCDIR/astdb.txt not found. Please verify the location."
exit 1
fi
# Start processing nodes
process_nodes
# Calculate total script execution time
end_time=$(date +%s%3N)
total_duration=$((end_time - start_time))
formatted_duration=$(format_total_time $total_duration)
echo "Total script execution time: $formatted_duration"
Thank You Mason, I will give this a try. Many thanks and 73, Steve WB4IZC
I run it every midnight via cron. The first time you run it, it will take forever. After that, it only updates the callsigns for nodes with new/updated info. It uses astdb.txt, so youâll need to read this: Other Software Products - AllStarLink Manual
Wanted to come and say Thanks...have been using this and meant to extend my gratitude.
Hey Mason-- Does that script need to live in a particular directory when I execute it..?
The only thing I remember from using it on HamVOIP was my default root partition was insufficient so I had to gpart it bigger; doesn't look like it'll be a problem on ASL3...
LATER: Nevermind, all OK, though OMG, you're correct; that takes a long time to process all the callsigns (+/â 1hr on a RPi-4 2MB). Working great!
I found this script on a Web search. It seems the -n option is ignored. For example, I am passing -n 519321 to the script but it proceeds to process the entire file! Is this a bug?
Great script. I just got it working. The link for enabling the needed services was very helpful. Thanks and 73, N9KIW.
I was telling a friend about this script. Heâs running a ClearNode with Hamvoip. Will this script work with Hamvoip or can it be modified to do so? Thanks.
HamVOIP (I think) comes with itâs own version, /usr/local/sbin/write_node_callsigns sh. If not bundled, than certainly thereâs a reference to add it on the HamVOIP website. Sorry, itâs been years since I did it last. If memory serves, I had to expand the / (root) filesystem because it wasnât big enough as created. YMMV.
HamVoIP has a built-in script to handle this.
/usr/local/sbin/write_node_callsigns.sh
The switches are similar to, but not identical to this script.
For example, there is no -f to force the script running, I.E. for automation purposes, because it doesnât prompt for anything, so there is no need for it.
The bad thing about HamVoIPâs version, though, is it doesnât do any comparing. So if a nodeâs call sign field has changed, it will skip that node if a file has already been generated for it, unless you specifically update just that node number, or all of them. So it doesnât take long before some things become stale, even if you keep it updating regularly.
It is, however, much faster than the referenced script for processing all nodes, because it just sticks files together using cat, not SoX, and doesnât do any kind of encoding or processing to the raw input or output files.
Speaking of that:
Since I make it a point to change the sound files on my ASL and HamVoIP nodes (I really hate Allison, the stock Asterisk voice), I also modified my local copy of this script to write out ulaw files instead of GSM, because the artifacts of GSM compression really bugs me. Like, seriously, I have an actual, visceral reaction to GSM artifacts. I wonder if Iâm the only one.
The files I use are much faster than stock, so Iâm not too much concerned about space, even with 43000 plus nodes.
I went to update things today; be advised the directory where astdb.txt lives has changed, so you need to change:
SRCDIR="/var/log/asterisk
to
SRCDIR="/var/lib/asterisk
I couldn't locate a astdb.txt in /var/lib/asterisk. There was only a file called astdb.sqlite3. I am running the latest ASL3 install with AllMon3 v1.91 on Trixie. RPi5. There is probably a switch somewhere to enable the .txt file to be created. I grabbed a copy from a HAMVoip install and the script does run.
The HamVoIP script won't work without modification anyway, because it is looking for GSM files that don't exist. Plus, it doesn't take changes to node numbers/call signs into account.
There is a script linked here that does, but it needs some optimizing.
You mis-understood. I didn't copy the HAMVoip script but the astdb.txt file. (The database text file). It doesn't exist on my new install of ASL3 but the script above requires it.
See ASL Manual : Other Software Products for information about the /var/lib/asterisk/astdb.txt file.
Thanks for the info. I had just started to research how I could get the file created on ASL3. I see that the file is updated on boot then 4 times a day using this mechanism which is great.
Ah well, so I did. In either case, when you do have astdb.txt (another post has info on making that happen with ASL3), the initial run of that script takes a while. Not as long as it did before the great node registration purge, but still much longer than it could.
Thanks for the heads up re the time to build the callsign data file the first time around. I did see a new timestamp on the file shortly after I installed the service. This only took a few minutes but it may have been doing a delta update on the file I copied over from HAMVoip. I will delete the file tonight and let it rebuild from scratch. There was a dependency that was missing when I ran the callsign/nodename script from above. This was 'bc' and easily installed with 'apt-get install bc'. Regarding the the HAMVoip version of the nodenames voice file builder, I have modified the original so that it uses .ul (ulaw) files. I also use the asl3-tts functionality, with one of the British accents, to create specific sound files for some of the well known hubs I connect to. These are stored in a protected directory and then copied back into the /var/lib/asterisk/sounds/rpt/nodenames location at the end of the update routine. These will be more wordy. For example "NWAG, the North West Allstar Group. Node id 40338, connected to, MyGateway node ID XXXX". Then mix this with the events stanza for a timestamp announcement following a connection change just prior. Great fun
#!/bin/bash
# write_node_callsigns.sh
#
# Original script by N5LSN
# Updated by Jory A. Pratt
#
# Make app_rpt telemetry use ASL callsigns instead of node numbers.
# Intended for use on ASL3.
#
# Updates vs original:
# - Parallel sox jobs (GNU parallel / xargs -P) for much faster first runs
# - Cached letter/digit audio paths (no per-character filesystem lookups)
# - Resume-safe: skip existing .gsm unless callsign changed or -r
# - Durable previous-db beside astdb.txt (not /tmp)
# - Auto-locate astdb in /var/lib/asterisk or /var/log/asterisk
# - Fixed -a / -n / -i / -v; added -j, -r, -s, -p
set -euo pipefail
SRCDIR=""
DESTDIR="/usr/share/asterisk/sounds/en/rpt/nodenames"
RPTSOUNDS="/usr/share/asterisk/sounds/en/rpt"
LETTERS="/usr/share/asterisk/sounds/en/letters"
NUMBERS="/usr/share/asterisk/sounds/en/digits"
PREV_DB=""
JOBS="$(nproc 2>/dev/null || echo 4)"
MAX_PREVIEW=10
VERBOSE=0
INCNODE=0
FORCE_RUN=0
FORCE_REBUILD=0
PROCESS_ALL=0
SINGLE_NODE=""
declare -A PREV_CALLSIGNS
usage() {
cat << EOF
Usage: write_node_callsigns.sh [options]
OPTIONS:
-h Show this message
-a Process all nodes (fill gaps; skips existing unless -r)
-i Append "node" + node number after the callsign
-n NODE Process a single node
-d PATH Destination directory (default: $DESTDIR)
-j N Parallel sox jobs (default: nproc)
-v Verbose output
-f Skip confirmation prompt
-r Force regenerate even when output already exists
-s PATH Directory containing astdb.txt
-p PATH Previous-db path used for change detection
Examples:
./write_node_callsigns.sh -a -f # First full build, no prompt
./write_node_callsigns.sh -f # Only new/changed nodes
./write_node_callsigns.sh -n 40000 -r # Rebuild one node
EOF
}
log() { printf '%s\n' "$*"; }
vlog() { (( VERBOSE )) && printf '%s\n' "$*" || true; }
die() { printf 'Error: %s\n' "$*" >&2; exit 1; }
resolve_astdb_dir() {
local dir candidates=()
[[ -n "$SRCDIR" ]] && candidates+=("$SRCDIR")
candidates+=("/var/lib/asterisk" "/var/log/asterisk")
for dir in "${candidates[@]}"; do
if [[ -f "$dir/astdb.txt" ]]; then
SRCDIR="$dir"
return 0
fi
done
return 1
}
ensure_directory_exists() {
if [[ ! -d "$1" ]]; then
log "Creating directory: $1"
mkdir -p "$1" || die "Failed to create directory $1"
fi
}
resolve_audio() {
local basepath=$1
if [[ -f "${basepath}.gsm" ]]; then
printf '%s\n' "${basepath}.gsm"
elif [[ -f "${basepath}.ulaw" ]]; then
printf '%s\n' "${basepath}.ulaw"
else
printf '\n'
fi
}
# Cache letter/digit paths into exported env vars for parallel workers.
# Keys: AUDIO_MAP_0..9, AUDIO_MAP_a..z, AUDIO_MAP_SLASH, AUDIO_MAP_DASH
build_audio_cache() {
local c file
for c in {0..9}; do
file="$(resolve_audio "$NUMBERS/$c")"
[[ -n "$file" ]] || die "Missing digit audio for '$c' under $NUMBERS"
export "AUDIO_MAP_$c=$file"
done
for c in {a..z}; do
file="$(resolve_audio "$LETTERS/$c")"
[[ -n "$file" ]] || die "Missing letter audio for '$c' under $LETTERS"
export "AUDIO_MAP_$c=$file"
done
file="$(resolve_audio "$LETTERS/slash")"
[[ -n "$file" ]] || die "Missing slash audio under $LETTERS"
export AUDIO_MAP_SLASH="$file"
file="$(resolve_audio "$LETTERS/dash")"
[[ -n "$file" ]] || die "Missing dash audio under $LETTERS"
export AUDIO_MAP_DASH="$file"
if (( INCNODE )); then
NODE_SOUND="$(resolve_audio "$RPTSOUNDS/node")"
[[ -n "$NODE_SOUND" ]] || die "Missing node audio under $RPTSOUNDS"
export NODE_SOUND
fi
export INCNODE DESTDIR VERBOSE
}
audio_lookup() {
case "$1" in
/) printf '%s' "${AUDIO_MAP_SLASH-}" ;;
-) printf '%s' "${AUDIO_MAP_DASH-}" ;;
*) local var="AUDIO_MAP_$1"; printf '%s' "${!var-}" ;;
esac
}
process_one_node() {
local node=$1
local callsign=${2,,}
local out="$DESTDIR/$node.gsm"
local -a inputs=()
local i char file
for (( i = 0; i < ${#callsign}; i++ )); do
char="${callsign:i:1}"
file="$(audio_lookup "$char")"
if [[ -z "$file" ]]; then
printf 'Error: No audio mapping for %q (node %s)\n' "$char" "$node" >&2
return 1
fi
if [[ "$file" == *.ulaw ]]; then
inputs+=(-t raw -e u-law -r 8000 -c 1 "$file")
else
inputs+=("$file")
fi
done
if (( INCNODE )); then
if [[ "${NODE_SOUND}" == *.ulaw ]]; then
inputs+=(-t raw -e u-law -r 8000 -c 1 "$NODE_SOUND")
else
inputs+=("$NODE_SOUND")
fi
for (( i = 0; i < ${#node}; i++ )); do
char="${node:i:1}"
file="$(audio_lookup "$char")"
if [[ -z "$file" ]]; then
printf 'Error: No audio mapping for %q (node %s)\n' "$char" "$node" >&2
return 1
fi
if [[ "$file" == *.ulaw ]]; then
inputs+=(-t raw -e u-law -r 8000 -c 1 "$file")
else
inputs+=("$file")
fi
done
fi
sox "${inputs[@]}" "$out" || {
printf 'sox failed for node %s (%s)\n' "$node" "$2" >&2
return 1
}
if (( VERBOSE )); then
printf 'OK %s %s\n' "$node" "$2"
else
printf '.'
fi
}
export -f process_one_node audio_lookup
load_previous_database() {
PREV_CALLSIGNS=()
[[ -f "$PREV_DB" ]] || return 0
local node callsign
while IFS='|' read -r node callsign _; do
[[ -n "$node" ]] || continue
[[ "$node" == \;* ]] && continue
PREV_CALLSIGNS["$node"]="$callsign"
done < "$PREV_DB"
}
# Unique node|callsign lines (last occurrence wins).
latest_astdb_entries() {
awk -F'|' '
/^;/ || NF < 2 || $1 == "" { next }
{ node[$1] = $2 }
END { for (n in node) print n "|" node[n] }
' "$SRCDIR/astdb.txt"
}
lookup_node_callsign() {
local want=$1
awk -F'|' -v want="$want" '
/^;/ || NF < 2 { next }
$1 == want { call = $2 }
END { if (call != "") print call }
' "$SRCDIR/astdb.txt"
}
# Return 0 if sox should run for this node.
needs_rebuild() {
local node=$1
local callsign=$2
local out="$DESTDIR/$node.gsm"
local old="${PREV_CALLSIGNS[$node]-}"
(( FORCE_REBUILD )) && return 0
[[ -f "$out" ]] || return 0
# Existing file: rebuild only when we know the callsign changed.
if [[ -n "$old" && "$old" != "$callsign" ]]; then
return 0
fi
return 1
}
queue_node() {
local node=$1
local callsign=$2
local old="${PREV_CALLSIGNS[$node]-}"
printf '%s|%s\n' "$node" "$callsign" >> "$WORK_FILE"
if [[ -z "$old" ]]; then
printf '%s: NEW -> %s\n' "$node" "$callsign" >> "$CHANGES_FILE"
else
printf '%s: %s -> %s\n' "$node" "$old" "$callsign" >> "$CHANGES_FILE"
fi
}
confirm_processing() {
local node_count=$1
(( FORCE_RUN )) && return 0
log "$node_count nodes need to be processed (jobs=$JOBS)."
log "Preview of changes:"
local i=0 line
while IFS= read -r line; do
log " $line"
(( ++i >= MAX_PREVIEW )) && { log " ...and more."; break; }
done < "$CHANGES_FILE"
local response
read -r -p "Continue? [y/n]: " response
case "$response" in
[yY]|[yY][eE][sS]) log "Starting processing..." ;;
*) log "Aborting."; exit 0 ;;
esac
}
run_parallel() {
local total
total="$(wc -l < "$WORK_FILE" | tr -d ' ')"
if command -v parallel >/dev/null 2>&1; then
parallel -j "$JOBS" --colsep '[|]' process_one_node {1} {2} < "$WORK_FILE"
else
while IFS='|' read -r node callsign; do
printf '%s\0%s\0' "$node" "$callsign"
done < "$WORK_FILE" \
| xargs -0 -n 2 -P "$JOBS" bash -c 'process_one_node "$1" "$2"' _
fi
(( VERBOSE )) || printf '\n'
log "Processed $total nodes."
}
update_previous_db() {
ensure_directory_exists "$(dirname "$PREV_DB")"
cp "$SRCDIR/astdb.txt" "$PREV_DB"
vlog "Updated previous DB: $PREV_DB"
}
format_total_time() {
local total_time_ms=$1
if (( total_time_ms < 1000 )); then
echo "${total_time_ms}ms"
elif (( total_time_ms < 60000 )); then
awk -v ms="$total_time_ms" 'BEGIN { printf "%.1fs\n", ms / 1000 }'
elif (( total_time_ms < 3600000 )); then
awk -v ms="$total_time_ms" 'BEGIN { printf "%.1fmin\n", ms / 60000 }'
else
awk -v ms="$total_time_ms" 'BEGIN { printf "%.1fh\n", ms / 3600000 }'
fi
}
# --- options ---
while getopts "haij:n:d:s:p:fvr" OPTION; do
case "$OPTION" in
h) usage; exit 0 ;;
a) PROCESS_ALL=1 ;;
i) INCNODE=1 ;;
j) JOBS="$OPTARG" ;;
n) SINGLE_NODE="$OPTARG" ;;
d) DESTDIR="$OPTARG" ;;
s) SRCDIR="$OPTARG" ;;
p) PREV_DB="$OPTARG" ;;
f) FORCE_RUN=1 ;;
v) VERBOSE=1 ;;
r) FORCE_REBUILD=1 ;;
*) usage; exit 1 ;;
esac
done
[[ "$JOBS" =~ ^[1-9][0-9]*$ ]] || die "-j must be a positive integer"
start_time="$(date +%s%3N)"
resolve_astdb_dir || die "astdb.txt not found (tried /var/lib/asterisk, /var/log/asterisk). Use -s PATH."
[[ -z "$PREV_DB" ]] && PREV_DB="$SRCDIR/previous_astdb.txt"
ensure_directory_exists "$DESTDIR"
command -v sox >/dev/null 2>&1 || die "sox is required but not installed"
build_audio_cache
load_previous_database
WORK_FILE="$(mktemp)"
CHANGES_FILE="$(mktemp)"
cleanup() { rm -f "$WORK_FILE" "$CHANGES_FILE"; }
trap cleanup EXIT
node_count=0
if [[ -n "$SINGLE_NODE" ]]; then
callsign="$(lookup_node_callsign "$SINGLE_NODE")"
[[ -n "$callsign" ]] || die "Node $SINGLE_NODE not found in $SRCDIR/astdb.txt"
if needs_rebuild "$SINGLE_NODE" "$callsign"; then
queue_node "$SINGLE_NODE" "$callsign"
node_count=1
fi
else
while IFS='|' read -r node callsign; do
[[ -n "$node" && -n "$callsign" ]] || continue
old="${PREV_CALLSIGNS[$node]-}"
if (( PROCESS_ALL )); then
# All nodes: write missing (or changed / -r) files.
if needs_rebuild "$node" "$callsign"; then
queue_node "$node" "$callsign"
(( ++node_count )) || true
else
vlog "Skipping $node ($callsign) â output exists"
fi
continue
fi
# Incremental: only new or changed callsigns.
if [[ -n "$old" && "$old" == "$callsign" ]]; then
continue
fi
if needs_rebuild "$node" "$callsign"; then
queue_node "$node" "$callsign"
(( ++node_count )) || true
else
vlog "Skipping $node ($callsign) â output exists (resume)"
fi
done < <(latest_astdb_entries)
fi
if (( node_count == 0 )); then
log "No nodes need processing."
if [[ ! -f "$PREV_DB" ]]; then
update_previous_db
fi
end_time="$(date +%s%3N)"
log "Total script execution time: $(format_total_time "$((end_time - start_time))")"
exit 0
fi
confirm_processing "$node_count"
run_parallel
update_previous_db
end_time="$(date +%s%3N)"
log "Total script execution time: $(format_total_time "$((end_time - start_time))")"
I have had this fixed up for a few months, this will descrease the initial generation time and also fixes the argv problems. Feel free to test it out and let me know if there are any other changes that would be beneficial.