-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathbash-aliases
More file actions
2875 lines (2538 loc) · 113 KB
/
bash-aliases
File metadata and controls
2875 lines (2538 loc) · 113 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
### This file ~/wsprdaemon/bash-aliases includes functions and aliases which are helpful in running and debugging WD systems
#
#
function wd-decoding-logs-times() {
ls -lt `find /dev/shm/wsprdaemon/recording.d/ -name decoding_daemon.log`
}
function wd-kiwi-logtimes() {
date
ls -l ~/wsprdaemon/watchdog_daemon.pid
ls -lt /dev/shm/wsprdaemon/recording.d/KIWI_*/*/decoding_daemon.log | tail -5
}
get-radiod-restart-logs() {
local show_previous=0
if [[ "${1:-}" == "-1" ]]; then
show_previous=1
fi
local services
services=$(systemctl list-units --type=service --state=running --no-legend | awk '/radiod@/ {print $1}')
if [[ -z "${services}" ]]; then
echo "No running radiod@* services found"
return 0
fi
local service
while read -r service; do
[[ -z "${service}" ]] && continue
local start_timestamp
start_timestamp=$(systemctl show -p ActiveEnterTimestamp --value "${service}")
echo "=== ${service} ==="
if (( show_previous )); then
# Show restarts up to and including current start
local until_time
until_time=$(date -d "${start_timestamp}" "+%Y-%m-%d %H:%M:%S")
sudo journalctl -u "${service}" --boot --until="${until_time}" --no-pager | grep -i "restart"
else
# Show restarts after current start
local since_time
since_time=$(date -d "${start_timestamp} + 1 second" "+%Y-%m-%d %H:%M:%S")
sudo journalctl -u "${service}" --since="${since_time}" --no-pager | grep -i "restart"
fi
echo "=== Started: ${start_timestamp} ==="
echo
done <<< "${services}"
}
alias chc='clickhouse-client'
alias ch=chc
wd-sftp-wd-test() {
local rc
local start_epoch
local end_epoch
local runtime_seconds
source ~/wsprdaemon/wsprdaemon.conf
if ! declare -p WD_SERVER_USER_LIST &> /dev/null; then
echo "ERROR: WD_SERVER_USER_LIST is not defined in ~/wsprdaemon/wsprdaemon.conf"
echo "This WD client is not configured to sftp upload extended spots to the WD servers"
return 1
fi
if [[ ${#WD_SERVER_USER_LIST[@]} -eq 0 ]]; then
echo "ERROR: WD_SERVER_USER_LIST is defined but empty"
return 1
fi
echo "Found WD_SERVER_USER_LIST with ${#WD_SERVER_USER_LIST[@]} server(s):"
for entry in "${WD_SERVER_USER_LIST[@]}"; do
echo " $entry"
done
echo ""
if [[ ! -d ~/.ssh ]]; then
echo "ERROR: The ~/.ssh directory which contains ssh keys doesn't exist."
echo "Run 'ssh-keygen -t ed25519 -N \"\"' to create them"
return 1
fi
local public_key_file_path_list=($(find ~/.ssh -name '*.pub'))
if [[ ${#public_key_file_path_list[@]} -eq 0 ]]; then
echo "ERROR: No ssh public key found in ~/.ssh"
echo "Run 'ssh-keygen -t ed25519 -N \"\"' to create one"
return 1
fi
local public_key_file_path="${public_key_file_path_list[0]}"
(( ${verbosity-0} )) && echo "Public key file: ${public_key_file_path}"
(( ${verbosity-0} )) && echo "Public key: $(< ${public_key_file_path})"
echo ""
local tmpfile=$(mktemp)
local testfile=$(mktemp)
echo "sftp test $(date)" > "${testfile}"
trap "rm -f '$tmpfile' '$testfile'" RETURN
local nc_timeout=${WD_SERVER_NC_TIMEOUT_SECONDS:-2}
local sftp_timeout=$(( nc_timeout * 10 ))
local overall_rc=0
for server_entry in "${WD_SERVER_USER_LIST[@]}"; do
local server_user="${server_entry%@*}"
local server_url="${server_entry#*@}"
echo "========================================"
echo "Testing: ${server_entry}"
echo "========================================"
# Test 1: TCP connectivity
echo " Testing TCP connectivity: nc -vz -w ${nc_timeout} ${server_url} 22"
start_epoch=${EPOCHSECONDS}
nc -vz -w ${nc_timeout} ${server_url} 22 &> ${tmpfile}
rc=$?
end_epoch=${EPOCHSECONDS}
runtime_seconds=$(( end_epoch - start_epoch ))
if (( rc )); then
printf " ✗ After %2d seconds, nc failed (rc=${rc}): %s\n" "${runtime_seconds}" "$(< ${tmpfile})"
overall_rc=1
echo ""
continue
fi
printf " ✓ After %2d seconds, TCP connection to ${server_url}:22 successful\n" "${runtime_seconds}"
# Test 2: SFTP autologin with REAL host key checking (matches upload function)
echo " Testing SFTP autologin (with host key check): sftp -o BatchMode=yes ${server_entry}"
start_epoch=${EPOCHSECONDS}
sftp -o BatchMode=yes -o ConnectTimeout=${sftp_timeout} -o StrictHostKeyChecking=accept-new -b /dev/null "${server_entry}" &> ${tmpfile}
rc=$?
end_epoch=${EPOCHSECONDS}
runtime_seconds=$(( end_epoch - start_epoch ))
if (( rc == 0 )); then
printf " ✓ After %2d seconds, SFTP autologin to ${server_entry} successful\n" "${runtime_seconds}"
else
# Check for host key issues
if grep -q "REMOTE HOST IDENTIFICATION HAS CHANGED" "${tmpfile}" 2>/dev/null; then
printf " ✗ After %2d seconds, SFTP failed - HOST KEY CHANGED\n" "${runtime_seconds}"
echo " The server's SSH key has changed. Run:"
echo " ssh-keygen -R ${server_url}"
echo " Then re-run this test to accept the new key."
elif grep -q "Host key verification failed\|not known\|No ED25519 host key" "${tmpfile}" 2>/dev/null; then
printf " ✗ After %2d seconds, SFTP failed - HOST KEY UNKNOWN\n" "${runtime_seconds}"
echo " Run: ssh-keyscan -H ${server_url} >> ~/.ssh/known_hosts"
else
printf " ✗ After %2d seconds, SFTP autologin failed (rc=${rc})\n" "${runtime_seconds}"
echo " Output: $(< ${tmpfile})"
echo ""
echo " Verify that this public key is registered on ${server_url}:"
echo " $(< ${public_key_file_path})"
fi
overall_rc=1
echo ""
continue
fi
# Test 3: Actual file upload/delete (catches permission issues)
echo " Testing file upload: put/rm test file"
start_epoch=${EPOCHSECONDS}
sftp -o BatchMode=yes -o ConnectTimeout=${sftp_timeout} -o StrictHostKeyChecking=accept-new "${server_entry}" <<EOF &> ${tmpfile}
put ${testfile} uploads/.sftp_test_$$
rm uploads/.sftp_test_$$
EOF
rc=$?
end_epoch=${EPOCHSECONDS}
runtime_seconds=$(( end_epoch - start_epoch ))
if (( rc == 0 )); then
printf " ✓ After %2d seconds, file upload test successful\n" "${runtime_seconds}"
else
printf " ✗ After %2d seconds, file upload test failed (rc=${rc})\n" "${runtime_seconds}"
echo " Output: $(< ${tmpfile})"
overall_rc=1
fi
echo ""
done
if (( overall_rc == 0 )); then
echo "========================================"
echo "✓ All ${#WD_SERVER_USER_LIST[@]} server(s) passed all tests"
echo "========================================"
else
echo "========================================"
echo "✗ Some servers failed tests"
echo "========================================"
fi
return ${overall_rc}
}
### TBD: This function should be used by assign_fft_cores()
function show_hyperthread_pairs()
{
local core_id=$1
# If a specific core is requested
if [[ -n $core_id ]]; then
if [[ -f /sys/devices/system/cpu/cpu${core_id}/topology/thread_siblings_list ]]; then
local siblings=$(cat /sys/devices/system/cpu/cpu${core_id}/topology/thread_siblings_list)
echo "CPU $core_id: siblings = $siblings"
else
echo "ERROR: CPU $core_id not found"
return 1
fi
return 0
fi
# Show all unique physical cores and their hyperthreads
declare -A seen
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
[[ -f $cpu/topology/thread_siblings_list ]] || continue
local core_num=$(basename $cpu | sed 's/cpu//')
local siblings=$(cat $cpu/topology/thread_siblings_list)
# Skip if we've already seen this sibling pair
[[ ${seen[$siblings]} ]] && continue
seen[$siblings]=1
echo "Physical core: $siblings"
done
}
### In ascending PID order restrict each fft thread to its own CPU core and restrict the fft's proc_888 thread and all 40+ the lin threads to
### the hyperthread pair of the FFT's core
### TBD: find the hyperthread pair. This code assumes it is CPU + 1
function assign_fft_cores() {
local cores=(0 2 4 6)
local core_index=0
echo "Assigning FFT threads and their siblings to cores..."
echo "==================================================="
# Find all threads named 'fft', sort by PID
local fft_pids=$(ps -eLo tid,comm | grep '[[:space:]]fft$' | awk '{print $1}' | sort -n)
if [ -z "$fft_pids" ]; then
echo "No threads named 'fft' found."
return 1
fi
# Assign each thread to a core
for pid in $fft_pids; do
local core=${cores[$core_index]}
local other_core=$(( core + 1 ))
# Set CPU affinity of FFT thread using taskset
if sudo taskset -cp $core $pid > /dev/null 2>&1; then
echo "FFT Thread $pid -> Core $core"
else
echo "FFT Failed to assign thread $pid to core $core"
fi
# Get the thread group ID (main process ID) from /proc
local tgid=$(grep "^Tgid:" /proc/$pid/status 2>/dev/null | awk '{print $2}')
if [ -z "$tgid" ]; then
echo "Could not determine TGID for thread $pid"
core_index=$(( (core_index + 1) % ${#cores[@]} ))
continue
fi
# Assign ALL other threads in the same process to the adjacent core
local assigned_count=0
if [ -d "/proc/$tgid/task" ]; then
for task in /proc/$tgid/task/*; do
local task_tid=$(basename "$task")
# Skip the fft thread itself
if [ "$task_tid" = "$pid" ]; then
continue
fi
# Assign this thread to the adjacent core
if sudo taskset -cp $other_core $task_tid > /dev/null 2>&1; then
local task_name=$(cat "$task/comm" 2>/dev/null)
echo " Thread $task_tid ($task_name) -> Core $other_core"
assigned_count=$(( assigned_count + 1 ))
fi
done
fi
echo " Assigned $assigned_count sibling threads to core $other_core"
echo ""
# Move to next core pair, wrap around if needed
core_index=$(( (core_index + 1) % ${#cores[@]} ))
done
echo "Assignment complete."
}
function rx888-info() {
# The RX888 uses the Cypress FX3 which has vendor ID 04b4, so get a list of Cypress devices
lsusb -d 04b4: 2>/dev/null | while read -r line; do
# Extract bus and device numbers
bus=$(echo "$line" | awk '{print $2}')
device=$(echo "$line" | awk '{print $4}' | tr -d ':')
product=$(echo "$line" | cut -d' ' -f7-)
# Get detailed info including serial number
serial=$(lsusb -v -s "$bus:$device" 2>/dev/null | grep "iSerial" | awk '{$1=$2=""; print $0}' | sed 's/^[[:space:]]*//')
if [ -z "$serial" ]; then
echo "WARNING: Cypress FX3 at USB $bus:$device reports no 'iSerial' line, so it appears not to be a RX888"
serial="(none)"
fi
### Find the/etc/radio/...*conf file which contains the line 'serial = $serial'
conf_file_path=$(grep "^serial.*$serial" /etc/radio/*.conf)
if [[ -z "$conf_file_path" ]]; then
printf "Rx?: 'no .conf file found with 'serial = $serial'"
else
conf_file_path=${conf_file_path%%.conf}
local rx_number="${conf_file_path: -1}"
printf "Rx${rx_number}: $bus:$device $serial is specfied in '$conf_file_path'\n"
fi
done
}
wd-ka9qctl() {
# Get all services from config files in /etc/radio/
local RADIOD_SERVICES=()
local DECODE_SERVICES=()
local RECORD_SERVICES=()
local REPORTER_SERVICES=()
local ALL_SERVICES=()
# Find radiod services
for conf in /etc/radio/radiod@*.conf; do
if [ -f "$conf" ]; then
local svc=$(basename "$conf" .conf).service
RADIOD_SERVICES+=("$svc")
fi
done
# Find ft4-decode and ft8-decode services
for conf in /etc/radio/ft4-decode*.conf /etc/radio/ft8-decode*.conf; do
if [ -f "$conf" ]; then
local svc=$(basename "$conf" .conf).service
DECODE_SERVICES+=("$svc")
fi
done
# Find ft4-record and ft8-record services
for conf in /etc/radio/ft4-record*.conf /etc/radio/ft8-record*.conf; do
if [ -f "$conf" ]; then
local svc=$(basename "$conf" .conf).service
RECORD_SERVICES+=("$svc")
fi
done
# Find pskreporter services (handle both formats: pskreporter@*.conf and *-pskreporter.conf)
for conf in /etc/radio/pskreporter*.conf /etc/radio/*-pskreporter.conf; do
if [ -f "$conf" ]; then
local basename=$(basename "$conf" .conf)
# Convert ft4-pskreporter to pskreporter@ft4 format
if [[ "$basename" == *-pskreporter ]]; then
local prefix=${basename%-pskreporter}
local svc="pskreporter@${prefix}.service"
else
local svc="${basename}.service"
fi
REPORTER_SERVICES+=("$svc")
fi
done
# Populate all services list
for svc in "${RADIOD_SERVICES[@]}"; do
ALL_SERVICES+=("$svc")
done
for svc in "${DECODE_SERVICES[@]}"; do
ALL_SERVICES+=("$svc")
done
for svc in "${RECORD_SERVICES[@]}"; do
ALL_SERVICES+=("$svc")
done
for svc in "${REPORTER_SERVICES[@]}"; do
ALL_SERVICES+=("$svc")
done
cmd_start() {
echo "Starting all KA9Q services..."
for svc in "${ALL_SERVICES[@]}"; do
echo " Starting $svc..."
sudo systemctl start "$svc" 2>/dev/null || true
done
echo "Done."
}
cmd_stop() {
echo "Stopping all KA9Q services..."
# Stop in reverse order
for ((i=${#ALL_SERVICES[@]}-1; i>=0; i--)); do
svc="${ALL_SERVICES[$i]}"
echo " Stopping $svc..."
sudo systemctl stop "$svc" 2>/dev/null || true
done
echo "Done."
}
cmd_restart() {
cmd_stop
sleep 2
cmd_start
}
cmd_status() {
printf "%-55s %-18s %s\n" "COMMAND" "STATUS" "UPTIME"
echo "--------------------------------------------------------------------------------------------"
# Check each radiod service
for svc in "${RADIOD_SERVICES[@]}"; do
check_service "sudo systemctl status $svc"
done
# Check decode services
for svc in "${DECODE_SERVICES[@]}"; do
check_service "sudo systemctl status $svc"
done
# Check record services
for svc in "${RECORD_SERVICES[@]}"; do
check_service "sudo systemctl status $svc"
done
# Check reporter services
for svc in "${REPORTER_SERVICES[@]}"; do
check_service "sudo systemctl status $svc"
done
}
check_service() {
local cmd="$1"
local service=$(echo "$cmd" | awk '{print $NF}')
if sudo systemctl is-active --quiet "$service" 2>/dev/null; then
local uptime=$(sudo systemctl show "$service" -p ActiveEnterTimestamp --value)
if [ -n "$uptime" ]; then
local seconds=$(($(date +%s) - $(date -d "$uptime" +%s)))
local hours=$((seconds / 3600))
local minutes=$(((seconds % 3600) / 60))
local secs=$((seconds % 60))
printf "%-55s [running] %dh %dm %ds\n" "$cmd" $hours $minutes $secs
else
printf "%-55s [running] \n" "$cmd"
fi
elif sudo systemctl is-enabled --quiet "$service" 2>/dev/null; then
printf "%-55s [stopped]\n" "$cmd"
else
local status=$(sudo systemctl status "$service" 2>&1)
if echo "$status" | grep -q "could not be found"; then
printf "%-55s [ERROR: none found]\n" "$cmd"
else
local rc=$(sudo systemctl show "$service" -p ExecMainStatus --value 2>/dev/null)
if [ -n "$rc" ] && [ "$rc" != "0" ]; then
printf "%-55s [ERROR: rc=%s]\n" "$cmd" "$rc"
else
printf "%-55s [stopped]\n" "$cmd"
fi
fi
fi
}
# Main command dispatcher
case "${1:-status}" in
start)
cmd_start
;;
stop)
cmd_stop
;;
restart)
cmd_restart
;;
status)
cmd_status
;;
*)
echo "Usage: wd-ka9qctl {start|stop|restart|status}"
echo ""
echo "Controls KA9Q radio daemon and associated services (from /etc/radio/*.conf):"
echo " - radiod@*.service"
echo " - ft4-decode*.service, ft8-decode*.service"
echo " - ft4-record*.service, ft8-record*.service"
echo " - pskreporter*.service"
return 1
;;
esac
}
#############################################
function wd-rac-status()
{
(cd ${HOME}/wsprdaemon/bin; ./frpc status)
}
function wd-rac-purge()
{
read -p "Are you *SURE* you want to purge any RAC connection? If you are remotely logged on, you may not be able to re-access this server [yN] =>"
REPLY=${REPLY:=N} ; REPLY=${REPLY:0:1} ; REPLY=${REPLY^}
if [[ ${REPLY} != "Y" ]]; then
echo "Aborting this command"
return 0
fi
sudo systemctl stop wd-remote-access.service
sudo systemctl disable wd_remote_access.service
sudo systemctl disable wd-remote-access.service
sudo rm -f /etc/systemd/system/wd-remote-access.service
sudo rm -f /etc/systemd/system/wd_remote_access.service
sudo systemctl daemon-reload
### Setup RAC again if it is defined in wsprdaemon.org
${HOME}/wsprdaemon/wsprdaemon.sh -V
}
function wd-ws8 ()
{
local ws8_usb_device="/dev/ttyACM0"
local attached_pids_list=($(sudo lsof ${ws8_usb_device} 2>&1 | awk '/^screen/{print $2}'))
if (( ${#attached_pids_list[@]} )); then
read -p "Found ${#attached_pids_list[@]} screen sessions attached to the WS8. Kill those sessions and connect? => "
sudo kill ${attached_pids_list[@]}
fi
sudo screen ${ws8_usb_device}
}
function wd-usbreset() {
if [[ -z "$1" ]]; then
echo "Usage: usbreset <bus-port | vendor:product>"
return 1
fi
# If user passed a device ID (like 04b4:00f1), find its bus-port
if [[ "$1" =~ : ]]; then
devpath=$(lsusb | grep "$1" | awk '{print $2"-"$4}' | sed 's/://')
if [[ -z "$devpath" ]]; then
echo "Device ID $1 not found"
return 1
fi
else
devpath="$1"
fi
echo "Resetting USB device at $devpath..."
echo -n "$devpath" | sudo tee /sys/bus/usb/drivers/usb/unbind
sleep 1
echo -n "$devpath" | sudo tee /sys/bus/usb/drivers/usb/bind
}
function wd-web-restart()
{
local web_daemon_pid="$(< ~/wsprdaemon/ka9q_web_daemon.pid)"
if [[ -z "$web_daemon_pid" ]]; then
echo "Ka9q-web is not running"
return 0
fi
local web_pid=$(ps --ppid "$web_daemon_pid" -o pid=)
kill $web_pid
}
function wd-most-recent-log-error()
{
local grep_arg="${1-ERROR:}"
local dir_list=( $(find . -type d ) )
local dir
for dir in ${dir_list[@]}; do
local log_file_list=( $( find ${dir} -maxdepth 1 -type f -name '*.log' ) )
local log_file
for log_file in ${log_file_list[@]}; do
local grep_last_line="$( grep "${grep_arg}" $(realpath ${log_file}) | grep -v start | tail -n 1)"
if [[ -n "${grep_last_line}" ]]; then
echo "${grep_last_line}"
fi
done
done
echo "$(date -u): search complete"
}
### If source and destination directory trees are on the same file system, wd-mvsync uses the much faster'mv' rather than 'rsync' to move the files
function wd-mvsync()
{
local src_dir=$1
local dst_dir=$2
local rc
local file
find "${src_dir}" -type f | while IFS= read -r file; do
local rel_path="${file#${src_dir}/}"
local dest_path="${dst_dir}/${rel_path}"
mkdir -p "$(dirname "$dest_path")"
mv "${file}" "${dest_path}"
rc=$? ; if (( rc )); then
echo "ERROR: 'mv ${file} ${dest_path}' => ${rc}"
return 1
fi
done
return 0
}
function wd-rsync()
{
local src_dir=${1-}
if [[ -z "${src_dir}" ]]; then
echo "Missing <SRC_DIR> argument. usage: wd-rsync <SRC_DIR> <DST_DIR>"
return
fi
if ! [[ -d "${src_dir}" ]]; then
echo "<SRC_DIR> doesn't exist. usage: wd-rsync <SRC_DIR> <DST_DIR>"
return
fi
local dst_dir=${2-}
if [[ -z "${dst_dir}" ]]; then
echo "Missing <DST_DIR> argument. usage: wd-rsync <SRC_DIR> <DST_DIR>"
return
fi
if ! [[ -d "${dst_dir}" ]]; then
echo "<DST_DIR> doesn't exist. usage: wd-rsync <SRC_DIR> <DST_DIR>"
return
fi
if (( $(stat -c %d "${src_dir}") == $(stat -c %d "${dst_dir}") )); then
echo "${src_dir} and ${dst_dir} are in the file system, so we can 'mv' the files rather than 'rsync' them"
wd-mvsync ${src_dir} ${dst_dir}
return
fi
echo "${src_dir} and ${dst_dir} are on different file systems, so we must run 'rsync' to transfer them"
echo "Executing: rsync -ah --remove-source-files --info=progress2 --stats ${src_dir} ${dst_dir}"
rsync -ah --remove-source-files --info=progress2 --stats "${src_dir}" "${dst_dir}"
local rc=$? ; if (( rc )); then
echo "ERROR: ' rsync -ah --remove-source-files --info=progress2 --stats ${src_dir} ${dst_dir}' => ${rc}"
return 1
fi
local src_files_list=( $(find ${src_dir} -type f) )
if (( ${#src_files_list[@]} ));then
echo "ERROR: found ${#src_files_list[@]} files in ${src_dir} after rsync"
return 2
fi
rm -r ${src_dir}
echo "All files in ${src_dir} have been moved to the corresponding subdir in ${dst_dir} and ${src_dir} has been deleted"
return 0
}
function wd-pcmrecord-restarts()
{
sudo journalctl --since "6 hours ago" -u radiod@rx888-wsprdaemon | grep "No rx888 data for 5 seconds, quitting"
}
function wd-pcmrecord-status()
{
ps -eo pid,lstart,etime,cmd | grep pcmrecord | grep -v grep
}
function cdt()
{
local wd_tmp_file_system_path
if [[ -d /run/wsprdaemon/recording.d ]]; then
wd_tmp_file_system_path="/run/wsprdaemon"
elif [[ -d /dev/shm/wsprdaemon/recording.d ]]; then
wd_tmp_file_system_path="/dev/shm/wsprdaemon"
else
echo "ERROR: dan't find WD's tmp file system"
return
fi
local recording_dir_path="${wd_tmp_file_system_path}/recording.d"
if ! [[ -d ${recording_dir_path} ]]; then
echo "Can't find expected directory '${recording_dir_path}'"
return
fi
local receiver_dir_path_list=( $(find ${recording_dir_path} -maxdepth 1 -type d -name 'KA9Q*') )
case ${#receiver_dir_path_list[@]} in
0)
echo "Can't find any 'KA9Q*' directories in '${recording_dir_path}'"
return 0
;;
1)
echo "Found one 'KA9Q*' directories in '${recording_dir_path}', so changing to it"
cd ${receiver_dir_path_list[0]}
;;
*)
printf "Found ${#receiver_dir_path_list[@]} 'KA9Q*' directories in '${recording_dir_path}'. Choose one:\n"
local index
for (( index=0; index < ${#receiver_dir_path_list[@]}; ++index )); do
printf "%2d: ${receiver_dir_path_list[index]}\n" ${index}
done
read -p "Select [0-$(( ${#receiver_dir_path_list[@]} - 1))]. Default is 0 => "
cd ${receiver_dir_path_list[${REPLY-0}]}
;;
esac
}
function wdsr()
{
local wd_is_running
local wd_last_start_epoch
local wd_watchdog_pid_file_path=~/wsprdaemon/watchdog_daemon.pid
if ! [[ -f ${wd_watchdog_pid_file_path} ]]; then
echo "Can't find '${wd_watchdog_pid_file_path}', so WD can't be running"
return 0
fi
local wd_pid=$(< ${wd_watchdog_pid_file_path})
local rc
local wd_run_time=$(ps -p ${wd_pid} -o etime=)
rc=$? ; if (( rc )) || [[ -z "${wd_run_time}" ]]; then
echo "WD's watchdog_daemon() pid ${wd_pid} is not running"
else
echo "WD's watchdog_daemon() pid ${wd_pid} has been running for D-HH:MM:SS: ${wd_run_time}"
fi
local pid_file_path_list=( $(find /dev/shm/wsprdaemon/ -path /dev/shm/wsprdaemon/wav-archive -prune -o -type f -name '*.pid') )
echo "Checking ${#pid_file_path_list[@]} pid files.."
local running_pid_count=0
local dead_pid_count=0
local pid_file
for pid_file in ${pid_file_path_list[@]} ; do
set +x
local pid=$(<${pid_file})
local pid_run_time=$(ps -p ${pid} -o etime= 2> /dev/null)
rc=$? ; set +x;if (( rc )) || [[ -z "${pid_run_time}" ]]; then
echo "PID ${pid} stored in ${pid_file} is not running"
(( ++dead_pid_count ))
else
(( ${verbosity-0} )) && echo "Deamon ${pid_file} is running"
(( ++running_pid_count ))
fi
done
echo "Found ${running_pid_count} running PID files and ${dead_pid_count} dead pids"
( source ~/wsprdaemon/running.jobs
echo "There are ${#RUNNING_JOBS[@]} running receiver jobs"
)
}
#shopt -s -o nounset
function get-github-projects-list()
{
# Extract the array definition into a temp file
local tmpfile=$1
awk '
BEGIN { in_array = 0 }
/^\s*declare\s+GITHUB_PROJECTS_LIST=\(/ { in_array = 1 }
in_array {
print
if (/\)\s*$/) {
in_array = 0
exit
}
}
' ~/wsprdaemon/ka9q-utils.sh > "$tmpfile"
}
function wd-commit-info()
{
local tmpfile=$(mktemp)
get-github-projects-list ${tmpfile}
# Now source the extracted array definition
# KA9Q_RADIO_COMMIT_CHECK="main"
unset KA9Q_RADIO_COMMIT_CHECK KA9Q_FT8_COMMIT_CHECK PSK_UPLOADER_COMMIT_CHECK ONION_COMMIT_CHECK KA9Q_WEB_COMMIT_CHECK
KA9Q_RADIO_LIBS_NEEDED="NONE"
ONION_LIBS_NEEDED="NONE"
source "$tmpfile"
ka9q_list=( "${GITHUB_PROJECTS_LIST[@]}")
source ~/wsprdaemon/wsprdaemon.conf
source "$tmpfile"
as_configured_list=( "${GITHUB_PROJECTS_LIST[@]}")
#cat "$tmpfile"
rm "$tmpfile"
local format="%20s %20s %42s %20s %42s %42s\n"
printf "${format}" "Service" "ka9q-utils.sh check" "ka9q-utils.sh commit" "as configured check" "as configured commit" "running commit"
local index
for (( index=0; index < ${#ka9q_list[@]}; ++index)); do
local ka9q_service_info_list=( ${ka9q_list[index]} )
local commit_dir=${ka9q_service_info_list[0]}
local ka9q_commit_check=${ka9q_service_info_list[1]}
local ka9q_commit_val=${ka9q_service_info_list[6]}
local as_configured_info_list=( ${as_configured_list[index]} )
local as_configured_commit_check=${as_configured_info_list[1]}
local as_configured_commit_val=${as_configured_info_list[6]}
local running_commit=$(cd ~/wsprdaemon/${commit_dir}; git rev-parse HEAD)
set +x
printf "${format}" ${commit_dir} ${ka9q_commit_check} ${ka9q_commit_val} ${as_configured_commit_check} ${as_configured_commit_val} ${running_commit}
done
}
function wd-cpu-watch()
{
watch -n 1 'bash -c '\''for cpu in $(ls -d /sys/devices/system/cpu/cpu[0-9]* | sort -V); do cur=$(( $(< $cpu/cpufreq/scaling_cur_freq)/1000 )); min=$(( $(< $cpu/cpufreq/scaling_min_freq)/1000 )); max=$(( $(< $cpu/cpufreq/scaling_max_freq)/1000 )); scaled=$(( cur * 80 / max )); stars=$(printf "%*s" "$scaled" "" | tr " " "*"); printf "%-6s %-80s cur=%5d MHz min=%5d MHz max=%5d MHz\n" "$(basename $cpu)" "$stars" "$cur" "$min" "$max"; done'\'''
}
function wd-cpu-info()
{
local sys_list=( $(ls -dv /sys/devices/system/cpu/cpu[0-9]*) ) ### Get numericallyt sorted list
local cpu_list=( ${sys_list[@]##*/} ) ### Chop off the poth to just get a list of core names
local sys_cpu_base_path="/sys/devices/system/cpu"
local cpu
for cpu in ${cpu_list[@]} ; do
local cpu_max_freq=$(cat "${sys_cpu_base_path}/${cpu}/cpufreq/scaling_max_freq" 2>/dev/null)
[ -n "${cpu_max_freq}" ] && printf "%5s: %s\n" "${cpu}" "${cpu_max_freq}"
done
printf "Available CPU frequencies: $(< /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies)\n"
}
function wd-cpu-set-core-speeds() {
local speed01="$1" # MHz for cores 0 and 1
local speedrest="$2" # MHz for all other cores
if [[ -z "$speed01" || -z "$speedrest" ]]; then
echo "Usage: set_core_speeds <speed-for-core0-1-MHz> <speed-for-rest-MHz>"
return 1
fi
# convert MHz → kHz
local s01=$((speed01 * 1000))
local srest=$((speedrest * 1000))
echo "🔧 Setting CPU0-1 to ${speed01} MHz, others to ${speedrest} MHz"
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
n=${cpu##*cpu} # extract number after "cpu"
if (( n == 0 || n == 1 )); then
echo "$s01" | sudo tee "$cpu/cpufreq/scaling_max_freq" > /dev/null
else
echo "$srest" | sudo tee "$cpu/cpufreq/scaling_max_freq" > /dev/null
fi
done
# Disable turbo boost (CPB)
if [[ -w /sys/devices/system/cpu/cpufreq/boost ]]; then
echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost > /dev/null
echo "🚫 Turbo Boost disabled"
else
echo "⚠️ Could not disable Turbo Boost: /sys/devices/system/cpu/cpufreq/boost not found"
fi
# Print summary table
echo
printf "%-5s %-10s\n" "CPU" "MaxFreq(MHz)"
echo "-------------------"
for cpu in /sys/devices/system/cpu/cpu[0-9]*; do
n=${cpu##*cpu}
freq=$(<"$cpu/cpufreq/scaling_max_freq")
printf "%-5s %-10s\n" "$n" "$((freq / 1000))"
done
}
function wd-fft-utils() {
local rc
local tmp_file="/tmp/wd-fft-utils.txt"
local service_name=$(systemctl list-units --no-legend 'radiod@*.service' | awk '{for(i=1;i<=NF;i++) if($i ~ /^radiod@/) print $i}')
sudo journalctl --no-pager -n 2000 -u ${service_name} > ${tmp_file}
rc=$? ; if (( rc )); then
echo "Can't find log lines for radiod. 'journalctl --nopager -n 2000 -u radiod@* > ${tmp_file}' returned error ${rc}"
return 1
fi
if [[ ! -s ${tmp_file} ]]; then
echo " 'journalctl --nopager -n 2000 -u radiod@* > ${tmp_file}' returned no error, but ${tmp_file} is empty"
return 2
fi
local tmp_file_size=$(wc -l < ${tmp_file})
(( ${verbosity-0} )) && echo "${tmp_file} has ${tmp_file_size} lines"
awk '/Start/ {last=NR; next} {lines[NR]=$0} END {for (i=last+1; i<=NR; i++) print lines[i]}' ${tmp_file} > ${tmp_file}.end
rc=$? ; if (( rc )); then
echo "awk '/Start/ {last=NR; next} {lines[NR]=$0} END {for (i=last+1; i<=NR; i++) print lines[i]}' ${tmp_file} > ${tmp_file}.end => ${rc}"
return 3
fi
if [[ ! -s ${tmp_file}.end ]]; then
echo "Couldn't find a 'Start' line in ${tmp_file}"
return 4
fi
local nominal_sample_rate=$(awk -F'nominal sample rate ' '/nominal sample rate/{print $2}' ${tmp_file}.end | awk '{print $1}')
local wisdom_fft_rates_list=($( awk -F'-o /tmp/wisdomf' '/-o \/tmp\/wisdomf /{print $2}' ${tmp_file}.end | awk '{print $1}' | sed 's/",//' | sort -u | sort -rn -k1.3 ) )
if (( ${#wisdom_fft_rates_list[@]} == 0 )); then
echo "FFT planning is optimal, so no update is needed"
return 0
fi
echo "Found $(wc -l < ${tmp_file}.end) lines and ${#wisdom_fft_rates_list[@]} unplanned rates reported after the most recent 'Start' line
nominal_sample_rate=${nominal_sample_rate}
wisdom_fft_rates_list=${wisdom_fft_rates_list[*]}"
read -p "Do you want to stop this WD server and run the FFT tool so the FFTs for those rates are optimized? [Y,n] => "
if [[ -n ${REPLY-} && ${REPLY^^} != "Y" ]]; then
echo "No changes have been made"
return 0
fi
wd-killall
echo "$(date): Running the FFT planning command. This may take minutes or even hours to complete. So leave this server undisturbed during this operation to maximize its optimizations:"
set -x
time fftwf-wisdom -v -T 1 -w /var/lib/ka9q-radio/wisdom -o /tmp/wisdomf ${wisdom_fft_rates_list[@]}
set +x
local current_wisdom_file_size=$(stat -c %s /etc/fftw/wisdomf)
local new_wisdom_file_size=$(stat -c %s /tmp/wisdomf)
echo "FFT optimization is complete
The newly created wisdom file /tmp/wisdom has ${new_wisdom_file_size} bytes
The currently installed versioon /etc/fftw/wisdom has ${current_wisdom_file_size} bytes"
if (( new_wisdom_file_size > current_wisdom_file_size )); then
read -p "Since the new wisdomf is larger than the current wisdomf, it is recommended that you install the new one. Proceed? [Y,n] => "
if [[ -z ${REPLY-} || ${REPLY^^} == "Y" ]]; then
sudo cp -p /etc/fftw/wisdomf /etc/fftw/wisdomf.save
sudo cp -p /tmp/wisdomf /etc/fftw/wisdomf
echo "/etc/fftw/wisdom has been changed"
else
echo "/etc/fftw/wisdom is unchanged"
fi
else
echo "ERROR: it is unexpected that the new wisdomf not larger than the current wisdomf, so do nothing"
fi
return 0
}
function wd-killweb() {
local web_pid=$(ps aux | grep web | grep -v grep | awk '{print $2}')
if [[ -z "${web_pid}" ]]; then
echo "Nor Ka9qQ-web process is running"
return 0
fi
sudo kill ${web_pid}
}
function wd-pcmrecord-version() {
~/wsprdaemon/ka9q-radio/pcmrecord --version
}
declare -A KA9Q_wspr_band_freq_list=(
[160]="1836600"
[80]="3568600"
[60]="5287200"
[40]="7038600"
[30]="10138700"
[20]="14095600"
[17]="18104600"
[15]="21094600"
[12]="24924600"
[10]="28124600"
[6]="50293000"
)
declare -A KA9Q_time_band_freq_list=(
[2.5]="2500000"
[5]="5000000"
[10]="10000000"
[15]="15000000"
[20]="20000000"
[25]="25000000"
[3.33]="330000"
[7.85]="785000"
[14.67]="14670000"
)
### USAGE: wd-pcmrecord-file-summaries [band]
### USAGE: wdpfs [band]
### USAGE: Runs the python program which validates the pcmrecord.log files Defaults to 20m for WSPR and 10 MHz for WWV
alias wdps='wd-pcmrecord-summaries'
function wd-pcmrecord-summaries()
{
local pcmrecord_log_dir
wd-get-ka9q-receiver-dir "pcmrecord_log_dir"
rc=$? && (( rc )) && echo "Can't find any KA9Q_* directories" && return $rc
local create_time_file_list=($( find $pcmrecord_log_dir -type f -name wav_file_create_time.log | grep WWV_[1-9] ))
if (( ${#create_time_file_list[@]} == 0 )); then
echo "Can't find any 'wav_file_create_time.log' files under $pcmrecord_log_dir"
return 1
fi
python3 ~/wsprdaemon/wd-validate-wav-logs.py ${create_time_file_list[@]}
}
### USAGE: wd-pcmrecord-filetime [band]
### USAGE: wdpf [band]
### USAGE: Lists the create and last write times of the wav files of the band. Defaults to 20m for WSPR and 10 MHz for WWV
alias wdpf='wd-pcmrecord-filetimes'
function wd-pcmrecord-filetimes()
{
local pcmrecord_log_dir
wd-get-ka9q-receiver-dir "pcmrecord_log_dir"
rc=$? && (( rc )) && echo "Can't find any KA9Q_* directories" && return $rc
local band
local tuning_freq
if [[ $pcmrecord_log_dir =~ WWV|CHU ]]; then
band=${1-10} ### Defaults to WWV_10
tuning_freq="${KA9Q_time_band_freq_list[${band}]}_iq"
else
band=${1-20} ### Defaults to 20m