-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrnsctl
More file actions
executable file
·1435 lines (1241 loc) · 46.4 KB
/
Copy pathrnsctl
File metadata and controls
executable file
·1435 lines (1241 loc) · 46.4 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
#!/usr/bin/env bash
# rnsctl v1.0 - TUI Mesh Operations Console
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# ===== Config & Defaults =====
SCRIPT_VERSION="1.0"
HOME_DIR="$HOME"
LOG_DIR="$HOME_DIR/rns_logs"
STATUS_SNAPSHOT="$HOME_DIR/rns-status.txt"
CFG_DIR="$HOME_DIR/.config/rnsctl"
NICKS_FILE="$CFG_DIR/nicknames.csv"
PORT_CACHE="$CFG_DIR/meshchat_port"
PEER_CACHE="$CFG_DIR/online_peers.json"
RETICULUM_CONFIG="${RETICULUM_CONFIG:-$HOME_DIR/.reticulum/config}"
# Whiptail configuration
WHIPTAIL_TITLE="Reticulum Mesh Control"
WHIPTAIL_HEIGHT=24
WHIPTAIL_WIDTH=80
WHIPTAIL_MENU_HEIGHT=14
# Universal MeshChat search paths - update this section
# if you stored reticulum-meshchat elsewhere!
MESHCHAT_DIRS=(
"/opt/reticulum-meshchat/storage"
"$HOME_DIR/reticulum-meshchat/storage"
"$HOME_DIR/.local/share/reticulum-meshchat/storage"
"$HOME_DIR/.config/reticulum-meshchat"
)
# Service name detection
RETICULUM_SERVICE=""
MESHCHAT_SERVICE=""
# ===== Utility Functions =====
cmd_exists(){ command -v "$1" >/dev/null 2>&1; }
ensure_dirs(){
mkdir -p "$LOG_DIR" "$CFG_DIR" 2>/dev/null || return 1
[[ -f "$LOG_DIR/rnsctl.log" ]] || touch "$LOG_DIR/rnsctl.log" 2>/dev/null || return 1
if [[ ! -f "$NICKS_FILE" ]]; then
cat >"$NICKS_FILE"<<'EOF' 2>/dev/null || return 1
EOF
fi
[[ -f "$PEER_CACHE" ]] || echo '{}' > "$PEER_CACHE" 2>/dev/null || return 1
}
log(){
printf '%s %s\n' "$(date '+%F %T')" "$*" >>"$LOG_DIR/rnsctl.log" 2>/dev/null || true
}
is_num(){
case "${1:-}" in
"" ) return 1;;
*[!0-9]* ) return 1;;
* ) return 0;;
esac
}
# ===== Whiptail Helpers =====
# Dark theme for whiptail using newt colors
export NEWT_COLORS='
root=white,black
window=white,black
border=white,black
shadow=black,black
title=white,black
button=black,white
actbutton=white,black
compactbutton=white,black
checkbox=white,black
actcheckbox=white,black
entry=white,black
label=white,black
listbox=white,black
actlistbox=white,black
textbox=white,black
acttextbox=white,black
helpline=white,black
roottext=white,black
emptyscale=white,black
fullscale=white,black
disentry=white,black
'
msg_box(){
local title="$1"
local text="$2"
whiptail --title "$title" --msgbox "$text" $WHIPTAIL_HEIGHT $WHIPTAIL_WIDTH
}
info_box(){
msg_box "Information" "$1"
}
error_box(){
msg_box "Error" "$1"
log "ERROR: $1"
}
success_box(){
msg_box "Success" "$1"
log "SUCCESS: $1"
}
yesno_box(){
local title="$1"
local text="$2"
whiptail --title "$title" --yesno "$text" $WHIPTAIL_HEIGHT $WHIPTAIL_WIDTH
}
input_box(){
local title="$1"
local text="$2"
local default="${3:-}"
whiptail --title "$title" --inputbox "$text" $WHIPTAIL_HEIGHT $WHIPTAIL_WIDTH "$default" 3>&1 1>&2 2>&3
}
text_viewer(){
local title="$1"
local file="$2"
whiptail --title "$title" --textbox "$file" $WHIPTAIL_HEIGHT $WHIPTAIL_WIDTH --scrolltext
}
gauge(){
local title="$1"
local text="$2"
whiptail --title "$title" --gauge "$text" 8 $WHIPTAIL_WIDTH 0
}
# ===== Service Detection =====
detect_services(){
if systemctl --user list-units --all 2>/dev/null | grep -qE "reticulum\.service|rnsd\.service"; then
if systemctl --user list-units --all 2>/dev/null | grep -q "reticulum\.service"; then
RETICULUM_SERVICE="reticulum.service"
elif systemctl --user list-units --all 2>/dev/null | grep -q "rnsd\.service"; then
RETICULUM_SERVICE="rnsd.service"
fi
elif systemctl list-units --all 2>/dev/null | grep -qE "reticulum\.service|rnsd\.service"; then
if systemctl list-units --all 2>/dev/null | grep -q "reticulum\.service"; then
RETICULUM_SERVICE="reticulum.service"
elif systemctl list-units --all 2>/dev/null | grep -q "rnsd\.service"; then
RETICULUM_SERVICE="rnsd.service"
fi
fi
if systemctl --user list-units --all 2>/dev/null | grep -q "meshchat\.service"; then
MESHCHAT_SERVICE="meshchat.service"
elif systemctl list-units --all 2>/dev/null | grep -q "meshchat\.service"; then
MESHCHAT_SERVICE="meshchat.service"
fi
log "Detected services: RETICULUM=$RETICULUM_SERVICE MESHCHAT=$MESHCHAT_SERVICE"
}
# ===== Privilege Management =====
SUDO_AVAILABLE=0; cmd_exists sudo && SUDO_AVAILABLE=1 || true
sudo_refresh(){
[[ "${SUDO_AVAILABLE:-0}" -eq 1 ]] || return 1
sudo -n -v 2>/dev/null && return 0
sudo -v
}
run_priv(){
if [[ "${SUDO_AVAILABLE:-0}" -eq 1 ]]; then
if sudo -n -v 2>/dev/null; then sudo "$@"; else sudo_refresh && sudo "$@"; fi
else
"$@"
fi
}
# ===== systemd Helpers =====
is_active_any(){
local unit="${1:-}"
[[ -z "$unit" ]] && return 1
systemctl --user is-active --quiet "$unit" 2>/dev/null && return 0
systemctl is-active --quiet "$unit" 2>/dev/null && return 0
return 1
}
get_service_status(){
local unit="$1"
if is_active_any "$unit"; then
echo "Running"
else
echo "Stopped"
fi
}
restart_any(){
local unit="${1:-}"
[[ -z "$unit" ]] && return 1
if systemctl --user list-units 2>/dev/null | grep -q "$unit"; then
systemctl --user restart "$unit" 2>/dev/null && return 0
fi
run_priv systemctl restart "$unit"
}
# ===== MeshChat DB Discovery =====
find_meshchat_db(){
local newest="" newest_mt=0
# Search for database.db in identities subdirectories
for root in "${MESHCHAT_DIRS[@]}"; do
if [[ ! -d "$root" ]]; then
continue
fi
# Look for identities/*/database.db pattern
if [[ -d "$root/identities" ]]; then
log "Searching for database in: $root/identities"
# Use find to locate all database.db files under identities/
while IFS= read -r -d '' db; do
# Verify it's actually a SQLite database with the right tables
if cmd_exists sqlite3; then
local tables
tables="$(sqlite3 "$db" ".tables" 2>/dev/null || true)"
# Check if it has the lxmf_messages table (confirms it's a MeshChat DB)
if [[ "$tables" == *"lxmf_messages"* ]]; then
# Get modification time to find the newest database
local mt
if stat -c %Y "$db" &>/dev/null; then
mt="$(stat -c %Y "$db" 2>/dev/null)"
elif stat -f %m "$db" &>/dev/null; then
mt="$(stat -f %m "$db" 2>/dev/null)"
else
mt=0
fi
is_num "$mt" || mt=0
if (( mt > newest_mt )); then
newest="$db"
newest_mt="$mt"
log "Found valid database: $db (mtime: $mt)"
fi
fi
else
# If sqlite3 isn't available, just take the first database.db we find
if [[ -z "$newest" ]]; then
newest="$db"
log "Found database (no sqlite3 verification): $db"
fi
fi
done < <(find "$root/identities" -type f -name "database.db" -print0 2>/dev/null)
fi
done
# Fallback: check for legacy/direct database.db locations
if [[ -z "$newest" ]]; then
for legacy_path in \
"/opt/reticulum-meshchat/storage/database.db" \
"$HOME_DIR/reticulum-meshchat/storage/database.db" \
"$HOME_DIR/.config/reticulum-meshchat/database.db" \
"$HOME_DIR/.local/share/reticulum-meshchat/database.db"; do
if [[ -f "$legacy_path" ]]; then
newest="$legacy_path"
log "Found database at legacy path: $legacy_path"
break
fi
done
fi
if [[ -n "$newest" ]]; then
echo "$newest"
return 0
fi
log "No database found in any search location"
return 1
}
mc_stats(){
local db="${1:-}"
if ! cmd_exists sqlite3; then echo "0|0|0||0"; return 0; fi
local m a p u last
m="$(sqlite3 "$db" "SELECT COUNT(*) FROM lxmf_messages;" 2>/dev/null || echo 0)"
a="$(sqlite3 "$db" "SELECT COUNT(*) FROM announces;" 2>/dev/null || echo 0)"
p="$(sqlite3 "$db" "SELECT COUNT(DISTINCT source_hash) FROM lxmf_messages;" 2>/dev/null || echo 0)"
u="$(sqlite3 "$db" "SELECT COUNT(*) FROM lxmf_messages WHERE is_incoming=1 AND state!='read';" 2>/dev/null || echo 0)"
last="$(sqlite3 "$db" "SELECT datetime(MAX(timestamp), 'unixepoch') FROM lxmf_messages;" 2>/dev/null || echo "")"
echo "${m}|${a}|${p}|${last}|${u}"
}
# ===== MeshChat API & Port Detection =====
detect_meshchat_port(){
if [[ -s "$PORT_CACHE" ]]; then
local cached; cached="$(cat "$PORT_CACHE" 2>/dev/null || true)"
if is_num "$cached" && (( cached > 1000 && cached < 65536 )); then
if curl -fsS --connect-timeout 2 "http://127.0.0.1:${cached}/api/v1/status" >/dev/null 2>&1; then
echo "$cached"
return 0
fi
fi
fi
if curl -fsS --connect-timeout 2 "http://127.0.0.1:8000/api/v1/status" >/dev/null 2>&1; then
echo "8000" | tee "$PORT_CACHE" >/dev/null 2>&1
return 0
fi
local ports=(3000 5000 8080 8888 9000)
for port in "${ports[@]}"; do
if curl -fsS --connect-timeout 2 "http://127.0.0.1:${port}/api/v1/status" >/dev/null 2>&1; then
echo "$port" | tee "$PORT_CACHE" >/dev/null 2>&1
return 0
fi
done
echo "8000" | tee "$PORT_CACHE" >/dev/null 2>&1
}
meshchat_api_ok(){
local port; port="$(detect_meshchat_port)"
curl -fsS --connect-timeout 3 "http://127.0.0.1:${port}/api/v1/status" >/dev/null 2>&1
}
# ===== Show Status =====
show_status(){
local db; db="$(find_meshchat_db 2>/dev/null || true)"
local m="0" a="0" p="0" last="" u="0"
if [[ -n "$db" && -f "$db" ]] && cmd_exists sqlite3; then
IFS='|' read -r m a p last u <<<"$(mc_stats "$db")"
fi
local rns_status="Unknown"
local rns_emoji="⚠"
[[ -n "$RETICULUM_SERVICE" ]] && {
rns_status="$(get_service_status "$RETICULUM_SERVICE")"
[[ "$rns_status" == "Running" ]] && rns_emoji="✓" || rns_emoji="✗"
}
local mc_status="Unknown"
local mc_emoji="⚠"
[[ -n "$MESHCHAT_SERVICE" ]] && {
mc_status="$(get_service_status "$MESHCHAT_SERVICE")"
[[ "$mc_status" == "Running" ]] && mc_emoji="✓" || mc_emoji="✗"
}
local api_status="Not Available"
local api_emoji="✗"
if meshchat_api_ok; then
api_status="Available"
api_emoji="✓"
fi
local wg_status="Not Connected"
local wg_emoji="✗"
if cmd_exists wg; then
local interfaces; interfaces="$(wg show interfaces 2>/dev/null || echo '')"
if [[ -n "$interfaces" ]]; then
wg_status="Connected"
wg_emoji="✓"
fi
fi
local health="GOOD"
local health_msg="All systems operational"
local problems=0
[[ "$rns_status" != "Running" ]] && ((problems++))
[[ "$mc_status" != "Running" ]] && ((problems++))
[[ "$api_status" != "Available" ]] && ((problems++))
if [[ $problems -gt 0 ]]; then
health="NEEDS ATTENTION"
health_msg="$problems system(s) need attention"
fi
local status_text="
═══════════════════════════════════════════════════
SYSTEM STATUS OVERVIEW
═══════════════════════════════════════════════════
Overall Health: $health
$health_msg
───────────────────────────────────────────────────
NETWORK SERVICES
───────────────────────────────────────────────────
$rns_emoji Mesh Network (Reticulum): $rns_status
The core mesh networking service
$mc_emoji Chat Service (MeshChat): $mc_status
Handles sending and receiving messages
$api_emoji Web Interface: $api_status
Lets you use the chat in your browser
───────────────────────────────────────────────────
YOUR MESSAGES
───────────────────────────────────────────────────
Total Messages: $m
Unread Messages: $u
People Chatted: $p"
[[ -n "$last" ]] && status_text="$status_text
Last Message: $last"
status_text="$status_text
───────────────────────────────────────────────────
VPN CONNECTION
───────────────────────────────────────────────────
$wg_emoji WireGuard VPN: $wg_status"
if [[ -z "$db" ]]; then
status_text="$status_text
───────────────────────────────────────────────────
WARNING
───────────────────────────────────────────────────
Message database not found. Chat may not be set up."
fi
if [[ $problems -gt 0 ]]; then
status_text="$status_text
───────────────────────────────────────────────────
SUGGESTED ACTION
───────────────────────────────────────────────────
Try restarting services from the 'Manage Services'
menu to fix any issues."
fi
msg_box "System Status" "$status_text"
}
# ===== Service Management =====
manage_services(){
while true; do
local rns_status="Unknown :("
local rns_desc=""
[[ -n "$RETICULUM_SERVICE" ]] && {
rns_status="$(get_service_status "$RETICULUM_SERVICE")"
[[ "$rns_status" == "Running" ]] && rns_desc=" Running" || rns_desc=" Stopped"
}
local mc_status="Unknown :("
local mc_desc=""
[[ -n "$MESHCHAT_SERVICE" ]] && {
mc_status="$(get_service_status "$MESHCHAT_SERVICE")"
[[ "$mc_status" == "Running" ]] && mc_desc=" Running" || mc_desc=" Stopped"
}
local choice
choice=$(whiptail --title "Manage Services" --menu \
"Current Status:
• Mesh Network: $rns_desc
• Chat Service: $mc_desc
What would you like to do?" \
22 78 10 \
"1" "Restart Mesh Network" \
"2" "Restart Chat Service" \
"3" "Restart Everything" \
"4" "View Error Logs" \
"5" "Check Service Health" \
"6" "◄ Back to Main Menu" \
3>&1 1>&2 2>&3)
case "$choice" in
1)
if [[ -z "$RETICULUM_SERVICE" ]]; then
error_box "Mesh network service not found on this system.\n\nPlease check your installation."
elif yesno_box "Restart Mesh Network" "This will restart your mesh radio connection.\n\nYou'll be offline for about 10 seconds.\n\nContinue?"; then
{
echo "0"; sleep 0.5
echo "30"; echo "# Stopping mesh network..."
sleep 1
echo "60"; echo "# Starting mesh network..."
restart_any "$RETICULUM_SERVICE"
sleep 1
echo "100"; echo "# Complete"
} | gauge "Restarting" "Restarting mesh network service..."
if is_active_any "$RETICULUM_SERVICE"; then
success_box "Mesh Network Restarted\n\nThe mesh radio is back online.\nYou should be able to connect to other devices now."
else
error_box "Restart Failed\n\nThe mesh network didn't start properly.\nCheck the logs for more information."
fi
fi
;;
2)
if [[ -z "$MESHCHAT_SERVICE" ]]; then
error_box "Chat service not found on this system.\n\nPlease check your installation."
elif yesno_box "Restart Chat Service" "This will restart the messaging system.\n\nAny messages being sent will be delayed.\n\nContinue?"; then
{
echo "0"; sleep 0.5
echo "30"; echo "# Stopping chat service..."
sleep 1
echo "60"; echo "# Starting chat service..."
restart_any "$MESHCHAT_SERVICE"
sleep 1
echo "100"; echo "# Complete"
} | gauge "Restarting" "Restarting chat service..."
if is_active_any "$MESHCHAT_SERVICE"; then
success_box "Chat Service Restarted\n\nMessaging is back online.\nYou can now send and receive messages."
else
error_box "Restart Failed\n\nThe chat service didn't start properly.\nCheck the logs for more information."
fi
fi
;;
3)
if yesno_box "Restart Everything" "This will restart ALL mesh services.\n\nYou'll be completely offline for 20-30 seconds.\n\nOnly do this if you're having serious problems.\n\nContinue?"; then
{
echo "0"; sleep 0.5
echo "20"; echo "# Stopping all services..."
sleep 1
echo "40"; echo "# Restarting mesh network..."
[[ -n "$RETICULUM_SERVICE" ]] && restart_any "$RETICULUM_SERVICE"
sleep 2
echo "70"; echo "# Restarting chat service..."
[[ -n "$MESHCHAT_SERVICE" ]] && restart_any "$MESHCHAT_SERVICE"
sleep 2
echo "90"; echo "# Verifying services..."
sleep 1
echo "100"; echo "# Complete"
} | gauge "Full Restart" "Restarting all services..."
local errors=0
[[ -n "$RETICULUM_SERVICE" ]] && ! is_active_any "$RETICULUM_SERVICE" && ((errors++))
[[ -n "$MESHCHAT_SERVICE" ]] && ! is_active_any "$MESHCHAT_SERVICE" && ((errors++))
if [[ $errors -eq 0 ]]; then
success_box "All Services Restarted\n\nEverything is back online.\nYour mesh connection should be working now."
else
error_box "Some Services Failed\n\n$errors service(s) didn't start properly.\n\nCheck the error logs for details."
fi
fi
;;
4)
view_logs_menu
;;
5)
service_health_check
;;
6|"")
return
;;
esac
done
}
view_logs_menu(){
local choice
choice=$(whiptail --title "View Error Logs" --menu \
"Logs help you understand what went wrong.
Choose which system to check:" \
18 78 6 \
"1" "Mesh Network Logs - Radio connection issues" \
"2" "Chat Service Logs - Messaging problems" \
"3" "System Control Logs - This program's log" \
"4" "◄ Back" \
3>&1 1>&2 2>&3)
case "$choice" in
1)
if [[ -n "$RETICULUM_SERVICE" ]]; then
if journalctl --user -u "$RETICULUM_SERVICE" -n 100 2>/dev/null > /tmp/rns_log.txt; then
text_viewer "Mesh Network Logs (Last 100 Lines)" /tmp/rns_log.txt
else
sudo journalctl -u "$RETICULUM_SERVICE" -n 100 > /tmp/rns_log.txt 2>/dev/null && \
text_viewer "Mesh Network Logs (Last 100 Lines)" /tmp/rns_log.txt || \
error_box "Could not read mesh network logs.\n\nYou may need administrator permissions."
fi
rm -f /tmp/rns_log.txt
else
error_box "Mesh network service not found.\n\nCannot display logs."
fi
;;
2)
if [[ -n "$MESHCHAT_SERVICE" ]]; then
if journalctl --user -u "$MESHCHAT_SERVICE" -n 100 2>/dev/null > /tmp/mc_log.txt; then
text_viewer "Chat Service Logs (Last 100 Lines)" /tmp/mc_log.txt
else
sudo journalctl -u "$MESHCHAT_SERVICE" -n 100 > /tmp/mc_log.txt 2>/dev/null && \
text_viewer "Chat Service Logs (Last 100 Lines)" /tmp/mc_log.txt || \
error_box "Could not read chat service logs.\n\nYou may need administrator permissions."
fi
rm -f /tmp/mc_log.txt
else
error_box "Chat service not found.\n\nCannot display logs."
fi
;;
3)
if [[ -f "$LOG_DIR/rnsctl.log" ]]; then
text_viewer "System Control Logs" "$LOG_DIR/rnsctl.log"
else
error_box "No system control logs found.\n\nLog file: $LOG_DIR/rnsctl.log"
fi
;;
esac
}
# ===== Service Health Check =====
service_health_check(){
local output="/tmp/service_health_$$.txt"
{
echo "0"; sleep 0.3
echo "20"; echo "# Checking mesh network..."
sleep 0.5
echo "40"; echo "# Checking chat service..."
sleep 0.5
echo "60"; echo "# Checking web interface..."
sleep 0.5
echo "80"; echo "# Checking database..."
sleep 0.5
echo "100"; echo "# Health check complete"
} | gauge "Service Health Check" "Diagnosing system health..."
local issues=()
local good=()
{
echo "════════════════════════════════════════"
echo " SERVICE HEALTH DIAGNOSTIC"
echo "════════════════════════════════════════"
echo ""
if [[ -n "$RETICULUM_SERVICE" ]]; then
if is_active_any "$RETICULUM_SERVICE"; then
echo " Mesh Network Service: HEALTHY"
good+=("Mesh network is running")
else
echo " Mesh Network Service: STOPPED"
issues+=("Mesh network is not running")
fi
else
echo " Mesh Network Service: NOT FOUND"
issues+=("Mesh network service not installed")
fi
if [[ -n "$MESHCHAT_SERVICE" ]]; then
if is_active_any "$MESHCHAT_SERVICE"; then
echo " Chat Service: HEALTHY"
good+=("Chat service is running")
else
echo "Chat Service: STOPPED"
issues+=("Chat service is not running")
fi
else
echo " Chat Service: NOT FOUND"
issues+=("Chat service not installed")
fi
if meshchat_api_ok; then
echo " Web Interface: HEALTHY"
good+=("Web interface is accessible")
else
echo " Web Interface: NOT RESPONDING"
issues+=("Web interface is not responding")
fi
local db; db="$(find_meshchat_db 2>/dev/null || true)"
if [[ -n "$db" && -f "$db" ]]; then
echo " Message Database: FOUND"
good+=("Message database is accessible")
else
echo " Message Database: NOT FOUND"
issues+=("Message database is missing")
fi
echo ""
echo "════════════════════════════════════════"
if [[ ${#issues[@]} -eq 0 ]]; then
echo " ALL SYSTEMS HEALTHY"
echo ""
echo "Everything is working correctly!"
echo "You're ready to use the mesh network."
else
echo " PROBLEMS DETECTED"
echo ""
echo "Issues found (${#issues[@]}):"
for issue in "${issues[@]}"; do
echo " $issue"
done
echo ""
echo "WHAT TO DO:"
echo " 1. Try restarting the affected services"
echo " 2. Check the error logs for details"
echo " 3. Contact support if problems persist"
fi
if [[ ${#good[@]} -gt 0 ]]; then
echo ""
echo "────────────────────────────────────────"
echo "Working systems (${#good[@]}):"
for item in "${good[@]}"; do
echo " • $item"
done
fi
} > "$output"
text_viewer "Service Health Report" "$output"
rm -f "$output"
}
# ===== Network Activity =====
view_network_activity(){
local db; db="$(find_meshchat_db 2>/dev/null || true)"
if [[ -z "$db" ]] || [[ ! -f "$db" ]]; then
error_box "Cannot find the message database.\n\nThe chat system may not be set up yet."
return
fi
if ! cmd_exists sqlite3; then
error_box "Database tools not available.\n\nCannot display network activity."
return
fi
local count
count="$(sqlite3 "$db" "SELECT COUNT(*) FROM announces WHERE updated_at > datetime('now', '-2 hours');" 2>/dev/null || echo 0)"
if [[ "$count" -eq 0 ]]; then
info_box "No Recent Network Activity\n\nNo other mesh devices have been detected in the last 2 hours.\n\nThis is normal if:\n • You just started the system\n • You're out of radio range\n • Other devices are offline"
return
fi
local tmpfile="/tmp/network_activity_$$.txt"
{
echo "MESH NETWORK - RECENT ACTIVITY"
echo "Found $count device(s) in the last 2 hours"
echo "========================================"
echo ""
sqlite3 "$db" "
SELECT
substr(destination_hash,1,12) as ID,
COALESCE(rssi, 'unknown') as Signal,
datetime(updated_at, 'localtime') as LastSeen,
CASE aspect
WHEN 'lxmf.delivery' THEN 'Chat Node'
WHEN 'lxmf.propagation' THEN 'Message Router'
ELSE 'Mesh Device'
END as DeviceType
FROM announces
WHERE updated_at > datetime('now', '-2 hours')
ORDER BY updated_at DESC
LIMIT 30;" 2>/dev/null | while IFS='|' read -r id signal seen type; do
if [[ -n "$id" ]]; then
echo "Device: $id"
echo " Type: $type"
echo " Signal: $signal dBm"
echo " Last Seen: $seen"
echo ""
fi
done
echo "========================================"
echo ""
echo "WHAT THIS MEANS:"
echo ""
echo " Chat Node = Another user you can message"
echo " Message Router = Helps deliver messages across the mesh"
echo " Mesh Device = Other network device"
echo ""
echo "Signal Strength Guide:"
echo " -50 to -70 dBm = Awesome"
echo " -70 to -85 dBm = Good"
echo " -85 to -95 dBm = Fair"
echo " Below -95 dBm = Weak"
echo ""
echo "NOTE: Devices come and go as people move around"
echo " or turn their radios on/off."
} > "$tmpfile"
text_viewer "Network Activity" "$tmpfile"
rm -f "$tmpfile"
}
# ===== View My Messages Summary =====
view_messages_summary(){
local db; db="$(find_meshchat_db 2>/dev/null || true)"
if [[ -z "$db" ]] || [[ ! -f "$db" ]]; then
error_box "Cannot find message database.\n\nThe chat system may not be set up yet."
return
fi
if ! cmd_exists sqlite3; then
error_box "Database tools not available."
return
fi
local tmpfile="/tmp/messages_summary_$.txt"
{
echo "════════════════════════════════════════"
echo " YOUR MESSAGES SUMMARY"
echo "════════════════════════════════════════"
echo ""
local total unread sent received
total="$(sqlite3 "$db" "SELECT COUNT(*) FROM lxmf_messages;" 2>/dev/null || echo 0)"
unread="$(sqlite3 "$db" "SELECT COUNT(*) FROM lxmf_messages WHERE is_incoming=1 AND state!='read';" 2>/dev/null || echo 0)"
sent="$(sqlite3 "$db" "SELECT COUNT(*) FROM lxmf_messages WHERE is_incoming=0;" 2>/dev/null || echo 0)"
received="$(sqlite3 "$db" "SELECT COUNT(*) FROM lxmf_messages WHERE is_incoming=1;" 2>/dev/null || echo 0)"
echo "MESSAGE STATISTICS"
echo "──────────────────────────────────────"
echo "Total Messages: $total"
echo "Sent by You: $sent"
echo "Received: $received"
echo "Unread: $unread"
echo ""
echo "RECENT CONVERSATIONS"
echo "──────────────────────────────────────"
local has_messages=0
sqlite3 "$db" "
SELECT DISTINCT
substr(CASE WHEN is_incoming=1 THEN source_hash ELSE dest_hash END, 1, 12) as contact,
COUNT(*) as msg_count,
MAX(datetime(timestamp, 'unixepoch', 'localtime')) as last_msg,
SUM(CASE WHEN is_incoming=1 AND state!='read' THEN 1 ELSE 0 END) as unread_cnt
FROM lxmf_messages
GROUP BY contact
ORDER BY MAX(timestamp) DESC
LIMIT 10;" 2>/dev/null | while IFS='|' read -r contact count last unread_cnt; do
if [[ -n "$contact" ]]; then
has_messages=1
echo ""
echo "Contact: $contact"
echo " Messages: $count"
echo " Unread: $unread_cnt"
echo " Last msg: $last"
fi
done
if [[ $has_messages -eq 0 ]]; then
echo ""
echo "No conversations yet."
echo ""
echo "Open the MeshChat web interface to start"
echo "chatting with others on the mesh network!"
fi
echo ""
echo "════════════════════════════════════════"
echo ""
echo "TIP: Open the web interface from the main"
echo " menu to read and send messages."
} > "$tmpfile"
text_viewer "Messages Summary" "$tmpfile"
rm -f "$tmpfile"
}
# ===== WireGuard Management =====
manage_wireguard(){
if ! cmd_exists wg; then
error_box "WireGuard tools not installed.\n\nInstall with: sudo apt install wireguard-tools"
return
fi
local tmpfile="/tmp/wg_status_$.txt"
{
echo "WIREGUARD VPN STATUS"
echo "===================="
echo ""
if wg show >/dev/null 2>&1; then
wg show
elif sudo -n wg show >/dev/null 2>&1; then
sudo wg show
else
echo "No WireGuard interfaces found or permission denied"
echo ""
echo "Network interfaces:"
ip addr show | grep -E "^[0-9]+: wg" -A2 || echo "No WireGuard interfaces configured"
fi
echo ""
echo "===================="
echo "Relevant network routes:"
ip route show | grep -E "(wg|10\.|172\.|192\.168\.)" | head -10 || echo "No relevant routes found"
} > "$tmpfile" 2>&1
text_viewer "WireGuard Status" "$tmpfile"
rm -f "$tmpfile"
}
# ===== Open MeshChat UI =====
open_meshchat_ui(){
local port; port="$(detect_meshchat_port)"
local url="http://127.0.0.1:${port}"
if ! meshchat_api_ok; then
error_box "MeshChat API is not responding.\n\nPlease check if MeshChat service is running."
return
fi
if yesno_box "Open Web Interface" "Open MeshChat in your web browser?\n\nURL: $url"; then
for browser in xdg-open open firefox chromium google-chrome brave-browser; do
if cmd_exists "$browser"; then
$browser "$url" >/dev/null 2>&1 &
success_box "MeshChat web interface opened in browser"
return
fi
done
info_box "Could not open browser automatically.\n\nPlease open this URL manually:\n$url"
fi
}
# ===== Generate Snapshot =====
generate_snapshot(){
local db; db="$(find_meshchat_db || true)"
{
echo "RETICULUM MESH NODE SNAPSHOT"
echo "Generated: $(date '+%F %T')"
echo "Version: rnsctl v${SCRIPT_VERSION}"
echo "Host: $(hostname)"
echo "User: $USER"
echo ""
echo "================================"
echo "SERVICES"
echo "================================"
if [[ -n "$RETICULUM_SERVICE" ]]; then
printf "%-25s %s\n" "Reticulum:" "$(is_active_any "$RETICULUM_SERVICE" && echo active || echo inactive)"
else
echo "Reticulum: not detected"
fi
if [[ -n "$MESHCHAT_SERVICE" ]]; then
printf "%-25s %s\n" "MeshChat:" "$(is_active_any "$MESHCHAT_SERVICE" && echo active || echo inactive)"
else
echo "MeshChat: not detected"
fi
echo ""
echo "================================"
echo "WIREGUARD VPN"
echo "================================"
if cmd_exists wg; then
wg show 2>/dev/null || sudo wg show 2>/dev/null || echo "No interfaces"
else
echo "WireGuard not installed"
fi
echo ""
echo "================================"
echo "MESHCHAT STATISTICS"
echo "================================"
if [[ -n "$db" && -f "$db" ]] && cmd_exists sqlite3; then
IFS='|' read -r m a p last u <<<"$(mc_stats "$db")"
echo "Database: $db"
echo "Total Messages: $m"
echo "Total Announces: $a"
echo "Unique Peers: $p"
echo "Unread Messages: $u"
echo "Last Message: $last"
else
echo "Database not found"
fi
} > "$STATUS_SNAPSHOT"
success_box "System snapshot saved to:\n$STATUS_SNAPSHOT"
}
# ===== System Check =====
system_check(){
local output="/tmp/system_check_$.txt"
{
echo "0"; sleep 0.5
echo "10"; echo "# Checking Reticulum service..."
sleep 0.5
echo "30"; echo "# Checking MeshChat service..."
sleep 0.5
echo "50"; echo "# Checking MeshChat API..."
sleep 0.5
echo "70"; echo "# Checking database..."
sleep 0.5
echo "90"; echo "# Finalizing check..."
sleep 0.5
echo "100"; echo "# Check complete"