plc-checkweigher 1.16.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/plc_checkweigher +468 -56
- package/package.json +1 -1
- package/setup.sh +158 -31
package/bin/plc_checkweigher
CHANGED
|
@@ -6,7 +6,8 @@ set -euo pipefail
|
|
|
6
6
|
|
|
7
7
|
INSTALL_DIR="/home/pi/plc_checkweigher"
|
|
8
8
|
PYTHON="/home/pi/plc_env/bin/python3"
|
|
9
|
-
|
|
9
|
+
DATA_DIR="${INSTALL_DIR}/data"
|
|
10
|
+
SMB_CFG="${DATA_DIR}/smb_config.py"
|
|
10
11
|
|
|
11
12
|
# ── TTY detection — animations only when connected to a real terminal ─────────
|
|
12
13
|
[[ -t 1 ]] && _TTY=1 || _TTY=0
|
|
@@ -151,7 +152,16 @@ case "$CMD" in
|
|
|
151
152
|
|
|
152
153
|
# ── System diagnostic ─────────────────────────────────────────────────────────
|
|
153
154
|
status|check|diag)
|
|
154
|
-
|
|
155
|
+
set +e
|
|
156
|
+
"${PYTHON}" "${INSTALL_DIR}/debugger.py" "$@"
|
|
157
|
+
_STATUS_EXIT=$?
|
|
158
|
+
set -e
|
|
159
|
+
if [[ $_STATUS_EXIT -ne 0 ]]; then
|
|
160
|
+
echo ""
|
|
161
|
+
warn "Errors detected — starting auto-fix ..."
|
|
162
|
+
sleep 1
|
|
163
|
+
"$0" fix
|
|
164
|
+
fi
|
|
155
165
|
;;
|
|
156
166
|
|
|
157
167
|
# ── Live logs ─────────────────────────────────────────────────────────────────
|
|
@@ -201,8 +211,8 @@ stop)
|
|
|
201
211
|
queue)
|
|
202
212
|
banner "SMB Delivery Queue"
|
|
203
213
|
echo ""
|
|
204
|
-
QUEUE="${
|
|
205
|
-
LEDGER="${
|
|
214
|
+
QUEUE="${DATA_DIR}/delivery_queue.json"
|
|
215
|
+
LEDGER="${DATA_DIR}/delivery_sent.log"
|
|
206
216
|
if [[ -f "$QUEUE" ]]; then
|
|
207
217
|
COUNT=$(python3 -c "import json; d=json.load(open('$QUEUE')); print(len(d))" 2>/dev/null || echo 0)
|
|
208
218
|
if [[ "$COUNT" -eq 0 ]]; then
|
|
@@ -577,7 +587,335 @@ smb-config)
|
|
|
577
587
|
;;
|
|
578
588
|
|
|
579
589
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
580
|
-
#
|
|
590
|
+
# FIX — auto-detect and repair common issues
|
|
591
|
+
# Usage: fix [-wifi] [-health] [-programs] (no flags = run all)
|
|
592
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
593
|
+
fix)
|
|
594
|
+
FIX_WIFI=0; FIX_HEALTH=0; FIX_PROGRAMS=0
|
|
595
|
+
if [[ $# -eq 0 ]]; then
|
|
596
|
+
FIX_WIFI=1; FIX_HEALTH=1; FIX_PROGRAMS=1
|
|
597
|
+
else
|
|
598
|
+
for _flag in "$@"; do
|
|
599
|
+
case "$_flag" in
|
|
600
|
+
-wifi) FIX_WIFI=1 ;;
|
|
601
|
+
-health) FIX_HEALTH=1 ;;
|
|
602
|
+
-programs) FIX_PROGRAMS=1 ;;
|
|
603
|
+
*) warn "Unknown flag: $_flag (valid: -wifi -health -programs)" ;;
|
|
604
|
+
esac
|
|
605
|
+
done
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
LOG_DIR="/home/pi/reports/logs"
|
|
609
|
+
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
|
610
|
+
# Build mode tag: "all" or hyphen-joined active scopes
|
|
611
|
+
_LOG_MODES=""
|
|
612
|
+
[[ $FIX_WIFI -eq 1 ]] && _LOG_MODES="${_LOG_MODES:+${_LOG_MODES}-}wifi"
|
|
613
|
+
[[ $FIX_HEALTH -eq 1 ]] && _LOG_MODES="${_LOG_MODES:+${_LOG_MODES}-}health"
|
|
614
|
+
[[ $FIX_PROGRAMS -eq 1 ]] && _LOG_MODES="${_LOG_MODES:+${_LOG_MODES}-}programs"
|
|
615
|
+
[[ "$_LOG_MODES" == "wifi-health-programs" ]] && _LOG_MODES="all"
|
|
616
|
+
LOG_FILE="${LOG_DIR}/fix_${_LOG_MODES}_$(date '+%Y%m%d_%H%M%S').log"
|
|
617
|
+
flog() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"; }
|
|
618
|
+
ffix_ok() { spin_ok "$*"; flog "FIXED : $*"; FIX_COUNT=$((FIX_COUNT+1)); }
|
|
619
|
+
ffix_info() { spin_ok "$*"; flog "INFO : $*"; }
|
|
620
|
+
ffix_warn() { spin_warn "$*"; flog "WARN : $*"; }
|
|
621
|
+
ffix_err() { spin_err "$*"; flog "ERROR : $*"; }
|
|
622
|
+
|
|
623
|
+
FIX_COUNT=0
|
|
624
|
+
flog "=== Run started | wifi=${FIX_WIFI} health=${FIX_HEALTH} programs=${FIX_PROGRAMS} ==="
|
|
625
|
+
|
|
626
|
+
banner "Auto Fix"
|
|
627
|
+
echo ""
|
|
628
|
+
need_sudo
|
|
629
|
+
|
|
630
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
631
|
+
# WIFI
|
|
632
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
633
|
+
if [[ $FIX_WIFI -eq 1 ]]; then
|
|
634
|
+
echo -e " ${B}▸ WiFi${NC}"; hr; echo ""
|
|
635
|
+
|
|
636
|
+
spin_start "NetworkManager"
|
|
637
|
+
if ! systemctl is-active --quiet NetworkManager 2>/dev/null; then
|
|
638
|
+
sudo systemctl start NetworkManager 2>/dev/null || true; sleep 2
|
|
639
|
+
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
|
|
640
|
+
ffix_ok "NetworkManager was down — restarted"
|
|
641
|
+
else
|
|
642
|
+
ffix_err "NetworkManager failed to start"
|
|
643
|
+
fi
|
|
644
|
+
else
|
|
645
|
+
ffix_info "NetworkManager running"
|
|
646
|
+
fi
|
|
647
|
+
|
|
648
|
+
spin_start "WiFi hardware block"
|
|
649
|
+
if rfkill list wifi 2>/dev/null | grep -q "Hard blocked: yes"; then
|
|
650
|
+
ffix_warn "Hard blocked — check physical WiFi switch"
|
|
651
|
+
elif rfkill list wifi 2>/dev/null | grep -q "Soft blocked: yes"; then
|
|
652
|
+
rfkill unblock wifi 2>/dev/null || true
|
|
653
|
+
ffix_ok "WiFi soft-block removed"
|
|
654
|
+
else
|
|
655
|
+
ffix_info "rfkill OK"
|
|
656
|
+
fi
|
|
657
|
+
|
|
658
|
+
spin_start "wlan0 interface"
|
|
659
|
+
if ! ip link show wlan0 &>/dev/null; then
|
|
660
|
+
ffix_warn "wlan0 not found — adapter missing?"
|
|
661
|
+
elif ! ip link show wlan0 2>/dev/null | grep -q "UP"; then
|
|
662
|
+
sudo ip link set wlan0 up 2>/dev/null || true; sleep 1
|
|
663
|
+
if ip link show wlan0 2>/dev/null | grep -q "UP"; then
|
|
664
|
+
ffix_ok "wlan0 was down — brought up"
|
|
665
|
+
else
|
|
666
|
+
ffix_err "Could not bring up wlan0"
|
|
667
|
+
fi
|
|
668
|
+
else
|
|
669
|
+
ffix_info "wlan0 UP"
|
|
670
|
+
fi
|
|
671
|
+
|
|
672
|
+
spin_start "WiFi connection"
|
|
673
|
+
_WIFI_CON=$(nmcli -t -f NAME,DEVICE,TYPE con show --active 2>/dev/null \
|
|
674
|
+
| grep ":wlan0:802-11-wireless" | cut -d: -f1 | head -1 || echo "")
|
|
675
|
+
if [[ -z "$_WIFI_CON" ]]; then
|
|
676
|
+
_LAST_CON=$(nmcli -t -f NAME,TYPE con show 2>/dev/null \
|
|
677
|
+
| grep ":802-11-wireless" | head -1 | cut -d: -f1 || echo "")
|
|
678
|
+
if [[ -n "$_LAST_CON" ]]; then
|
|
679
|
+
_spin_kill
|
|
680
|
+
spin_start "Reconnecting to '${_LAST_CON}'"
|
|
681
|
+
sudo nmcli connection up "$_LAST_CON" 2>/dev/null || true; sleep 3
|
|
682
|
+
_WIFI_NOW=$(nmcli -t -f NAME,DEVICE,TYPE con show --active 2>/dev/null \
|
|
683
|
+
| grep ":wlan0:802-11-wireless" | cut -d: -f1 | head -1 || echo "")
|
|
684
|
+
if [[ -n "$_WIFI_NOW" ]]; then
|
|
685
|
+
_NEW_IP=$(ip -4 addr show wlan0 2>/dev/null \
|
|
686
|
+
| grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "")
|
|
687
|
+
ffix_ok "Reconnected to '${_WIFI_NOW}' (IP: ${_NEW_IP:-?})"
|
|
688
|
+
else
|
|
689
|
+
ffix_warn "Could not reconnect — run: plc_checkweigher wifi"
|
|
690
|
+
fi
|
|
691
|
+
else
|
|
692
|
+
ffix_warn "No saved connections — run: plc_checkweigher wifi"
|
|
693
|
+
fi
|
|
694
|
+
else
|
|
695
|
+
_WIFI_IP=$(ip -4 addr show wlan0 2>/dev/null \
|
|
696
|
+
| grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "?")
|
|
697
|
+
ffix_info "'${_WIFI_CON}' (${_WIFI_IP})"
|
|
698
|
+
fi
|
|
699
|
+
|
|
700
|
+
spin_start "Internet connectivity"
|
|
701
|
+
if ping -c 2 -W 2 8.8.8.8 &>/dev/null; then
|
|
702
|
+
ffix_info "Internet reachable"
|
|
703
|
+
else
|
|
704
|
+
ffix_warn "No internet — local LAN may still work"
|
|
705
|
+
fi
|
|
706
|
+
|
|
707
|
+
echo ""
|
|
708
|
+
fi
|
|
709
|
+
|
|
710
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
711
|
+
# HEALTH
|
|
712
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
713
|
+
if [[ $FIX_HEALTH -eq 1 ]]; then
|
|
714
|
+
echo -e " ${B}▸ System Health${NC}"; hr; echo ""
|
|
715
|
+
|
|
716
|
+
spin_start "Disk space"
|
|
717
|
+
_DISK_PCT=$(df / --output=pcent 2>/dev/null | tail -1 | tr -d ' %' || echo "0")
|
|
718
|
+
if [[ "${_DISK_PCT:-0}" -ge 95 ]]; then
|
|
719
|
+
sudo journalctl --vacuum-size=100M 2>/dev/null || true
|
|
720
|
+
_DISK_AFTER=$(df / --output=pcent 2>/dev/null | tail -1 | tr -d ' %' || echo "?")
|
|
721
|
+
ffix_ok "Disk ${_DISK_PCT}% → ${_DISK_AFTER}% after journal clean"
|
|
722
|
+
elif [[ "${_DISK_PCT:-0}" -ge 85 ]]; then
|
|
723
|
+
ffix_warn "Disk ${_DISK_PCT}% — consider removing old reports"
|
|
724
|
+
else
|
|
725
|
+
ffix_info "Disk ${_DISK_PCT}%"
|
|
726
|
+
fi
|
|
727
|
+
|
|
728
|
+
spin_start "CPU temperature"
|
|
729
|
+
if command -v vcgencmd &>/dev/null; then
|
|
730
|
+
_TEMP=$(vcgencmd measure_temp 2>/dev/null | grep -oP '[\d.]+' | head -1 || echo "0")
|
|
731
|
+
_TEMP_INT=${_TEMP%.*}
|
|
732
|
+
if [[ "${_TEMP_INT:-0}" -ge 80 ]]; then
|
|
733
|
+
ffix_warn "${_TEMP}°C — check ventilation"
|
|
734
|
+
else
|
|
735
|
+
ffix_info "${_TEMP}°C"
|
|
736
|
+
fi
|
|
737
|
+
else
|
|
738
|
+
ffix_info "Temperature check not available"
|
|
739
|
+
fi
|
|
740
|
+
|
|
741
|
+
spin_start "Memory"
|
|
742
|
+
_MEM_FREE=$(free -m 2>/dev/null | awk '/^Mem:/{print $7}' || echo "999")
|
|
743
|
+
_MEM_TOTAL=$(free -m 2>/dev/null | awk '/^Mem:/{print $2}' || echo "1")
|
|
744
|
+
if [[ "${_MEM_FREE:-999}" -lt 50 ]]; then
|
|
745
|
+
echo 3 | sudo tee /proc/sys/vm/drop_caches >/dev/null 2>&1 || true
|
|
746
|
+
_MEM_AFTER=$(free -m 2>/dev/null | awk '/^Mem:/{print $7}' || echo "?")
|
|
747
|
+
ffix_ok "Low memory ${_MEM_FREE}MB → ${_MEM_AFTER}MB after cache drop"
|
|
748
|
+
else
|
|
749
|
+
ffix_info "${_MEM_FREE}MB / ${_MEM_TOTAL}MB free"
|
|
750
|
+
fi
|
|
751
|
+
|
|
752
|
+
spin_start "System time"
|
|
753
|
+
if timedatectl status 2>/dev/null | grep -q "synchronized: yes"; then
|
|
754
|
+
ffix_info "NTP synchronized"
|
|
755
|
+
else
|
|
756
|
+
sudo timedatectl set-ntp true 2>/dev/null || true
|
|
757
|
+
command -v chronyc &>/dev/null && sudo chronyc makestep 2>/dev/null || true
|
|
758
|
+
sleep 2
|
|
759
|
+
if timedatectl status 2>/dev/null | grep -q "synchronized: yes"; then
|
|
760
|
+
ffix_ok "NTP sync restored"
|
|
761
|
+
else
|
|
762
|
+
ffix_warn "Time not synced — no internet?"
|
|
763
|
+
fi
|
|
764
|
+
fi
|
|
765
|
+
|
|
766
|
+
spin_start "Reports directory"
|
|
767
|
+
_REPORTS_DIR="/home/pi/reports"
|
|
768
|
+
if [[ ! -d "$_REPORTS_DIR" ]]; then
|
|
769
|
+
mkdir -p "$_REPORTS_DIR" 2>/dev/null && ffix_ok "Created ${_REPORTS_DIR}" \
|
|
770
|
+
|| ffix_warn "Could not create ${_REPORTS_DIR}"
|
|
771
|
+
else
|
|
772
|
+
ffix_info "${_REPORTS_DIR} OK"
|
|
773
|
+
fi
|
|
774
|
+
|
|
775
|
+
spin_start "SMB config file"
|
|
776
|
+
mkdir -p "${DATA_DIR}" 2>/dev/null || true
|
|
777
|
+
if [[ ! -f "$SMB_CFG" ]]; then
|
|
778
|
+
printf 'SMB_ENABLED = False\nSMB_HOST = ""\nSMB_SHARE = "Reports"\nSMB_USERNAME = ""\nSMB_PASSWORD = ""\nSMB_SUBDIR = ""\n' \
|
|
779
|
+
> "$SMB_CFG" 2>/dev/null || sudo tee "$SMB_CFG" > /dev/null << 'SMBC'
|
|
780
|
+
SMB_ENABLED = False
|
|
781
|
+
SMB_HOST = ""
|
|
782
|
+
SMB_SHARE = "Reports"
|
|
783
|
+
SMB_USERNAME = ""
|
|
784
|
+
SMB_PASSWORD = ""
|
|
785
|
+
SMB_SUBDIR = ""
|
|
786
|
+
SMBC
|
|
787
|
+
ffix_ok "Created default smb_config.py (disabled — run: plc_checkweigher smb-config)"
|
|
788
|
+
else
|
|
789
|
+
ffix_info "smb_config.py present"
|
|
790
|
+
fi
|
|
791
|
+
|
|
792
|
+
echo ""
|
|
793
|
+
fi
|
|
794
|
+
|
|
795
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
796
|
+
# PROGRAMS
|
|
797
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
798
|
+
if [[ $FIX_PROGRAMS -eq 1 ]]; then
|
|
799
|
+
echo -e " ${B}▸ Programs & Services${NC}"; hr; echo ""
|
|
800
|
+
|
|
801
|
+
for _SVC in plc_watcher plc_web; do
|
|
802
|
+
spin_start "${_SVC}"
|
|
803
|
+
_SVC_STATE=$(systemctl is-active "$_SVC" 2>/dev/null || echo "inactive")
|
|
804
|
+
if [[ "$_SVC_STATE" != "active" ]]; then
|
|
805
|
+
sudo systemctl enable "$_SVC" 2>/dev/null || true
|
|
806
|
+
sudo systemctl restart "$_SVC" 2>/dev/null || true; sleep 2
|
|
807
|
+
_SVC_NOW=$(systemctl is-active "$_SVC" 2>/dev/null || echo "inactive")
|
|
808
|
+
if [[ "$_SVC_NOW" == "active" ]]; then
|
|
809
|
+
ffix_ok "${_SVC} restarted (was: ${_SVC_STATE})"
|
|
810
|
+
else
|
|
811
|
+
ffix_err "${_SVC} failed — check: journalctl -u ${_SVC} -n 30"
|
|
812
|
+
fi
|
|
813
|
+
else
|
|
814
|
+
ffix_info "${_SVC} active"
|
|
815
|
+
fi
|
|
816
|
+
done
|
|
817
|
+
|
|
818
|
+
spin_start "Python venv"
|
|
819
|
+
if [[ ! -x "$PYTHON" ]]; then
|
|
820
|
+
ffix_err "Venv missing at ${PYTHON} — re-run installer"
|
|
821
|
+
else
|
|
822
|
+
ffix_info "Venv OK"
|
|
823
|
+
fi
|
|
824
|
+
|
|
825
|
+
spin_start "Python packages"
|
|
826
|
+
_PKG_MISSING=""
|
|
827
|
+
for _pkg in flask reportlab pymcprotocol impacket; do
|
|
828
|
+
"${PYTHON}" -c "import ${_pkg}" &>/dev/null 2>&1 || _PKG_MISSING="${_PKG_MISSING} ${_pkg}"
|
|
829
|
+
done
|
|
830
|
+
if [[ -n "$_PKG_MISSING" ]]; then
|
|
831
|
+
_spin_kill
|
|
832
|
+
spin_start "Installing:${_PKG_MISSING}"
|
|
833
|
+
"${PYTHON}" -m pip install ${_PKG_MISSING} --quiet 2>/dev/null || true
|
|
834
|
+
_PKG_STILL=""
|
|
835
|
+
for _pkg in ${_PKG_MISSING}; do
|
|
836
|
+
"${PYTHON}" -c "import ${_pkg}" &>/dev/null 2>&1 || _PKG_STILL="${_PKG_STILL} ${_pkg}"
|
|
837
|
+
done
|
|
838
|
+
if [[ -z "$_PKG_STILL" ]]; then
|
|
839
|
+
ffix_ok "Installed:${_PKG_MISSING}"
|
|
840
|
+
else
|
|
841
|
+
ffix_warn "Could not install:${_PKG_STILL}"
|
|
842
|
+
fi
|
|
843
|
+
else
|
|
844
|
+
ffix_info "All packages present"
|
|
845
|
+
fi
|
|
846
|
+
|
|
847
|
+
spin_start "Delivery queue"
|
|
848
|
+
_QUEUE="${DATA_DIR}/delivery_queue.json"
|
|
849
|
+
mkdir -p "${DATA_DIR}" 2>/dev/null || true
|
|
850
|
+
if [[ ! -f "$_QUEUE" ]]; then
|
|
851
|
+
echo "[]" > "$_QUEUE" 2>/dev/null || true
|
|
852
|
+
ffix_ok "Created missing delivery_queue.json"
|
|
853
|
+
elif ! "${PYTHON}" -c "import json; json.load(open('${_QUEUE}'))" &>/dev/null 2>&1; then
|
|
854
|
+
mv "$_QUEUE" "${_QUEUE}.broken.$(date +%s)" 2>/dev/null || true
|
|
855
|
+
echo "[]" > "$_QUEUE" 2>/dev/null || true
|
|
856
|
+
ffix_ok "Corrupt delivery queue reset (backup saved)"
|
|
857
|
+
else
|
|
858
|
+
ffix_info "delivery_queue.json valid"
|
|
859
|
+
fi
|
|
860
|
+
|
|
861
|
+
spin_start "systemd unit files"
|
|
862
|
+
_RESTORED=""
|
|
863
|
+
for _src in "${INSTALL_DIR}/plc_watcher.service" "${INSTALL_DIR}/web/plc_web.service"; do
|
|
864
|
+
[[ ! -f "$_src" ]] && continue
|
|
865
|
+
_dst="/etc/systemd/system/$(basename "$_src")"
|
|
866
|
+
if [[ ! -f "$_dst" ]]; then
|
|
867
|
+
sudo cp "$_src" "$_dst" 2>/dev/null || true
|
|
868
|
+
_RESTORED="${_RESTORED} $(basename "$_src")"
|
|
869
|
+
fi
|
|
870
|
+
done
|
|
871
|
+
if [[ -n "$_RESTORED" ]]; then
|
|
872
|
+
sudo systemctl daemon-reload 2>/dev/null || true
|
|
873
|
+
ffix_ok "Restored:${_RESTORED}"
|
|
874
|
+
else
|
|
875
|
+
ffix_info "Unit files present"
|
|
876
|
+
fi
|
|
877
|
+
|
|
878
|
+
echo ""
|
|
879
|
+
fi
|
|
880
|
+
|
|
881
|
+
# ── Summary ───────────────────────────────────────────────────────────────
|
|
882
|
+
flog "=== Complete: ${FIX_COUNT} fix(es) applied ==="
|
|
883
|
+
hr
|
|
884
|
+
echo ""
|
|
885
|
+
if [[ $FIX_COUNT -gt 0 ]]; then
|
|
886
|
+
ok "${FIX_COUNT} fix(es) applied"
|
|
887
|
+
else
|
|
888
|
+
ok "All checks passed — no fixes needed"
|
|
889
|
+
fi
|
|
890
|
+
info "Log: ${LOG_FILE}"
|
|
891
|
+
|
|
892
|
+
# ── Push log to SMB share ─────────────────────────────────────────────────
|
|
893
|
+
_SMB_HOST=$(smb_get "SMB_HOST")
|
|
894
|
+
_SMB_SHARE=$(smb_get "SMB_SHARE")
|
|
895
|
+
_SMB_USER=$(smb_get "SMB_USERNAME")
|
|
896
|
+
_SMB_PASS=$(smb_get "SMB_PASSWORD")
|
|
897
|
+
_SMB_EN=$(grep "^SMB_ENABLED" "${SMB_CFG}" 2>/dev/null | grep -qi "true" && echo "1" || echo "0")
|
|
898
|
+
|
|
899
|
+
if [[ "$_SMB_EN" == "1" && -n "$_SMB_HOST" && -n "$_SMB_USER" ]] \
|
|
900
|
+
&& command -v smbclient &>/dev/null; then
|
|
901
|
+
spin_start "Uploading log to SMB share"
|
|
902
|
+
_LOG_BASENAME="$(basename "$LOG_FILE")"
|
|
903
|
+
if smbclient "//${_SMB_HOST}/${_SMB_SHARE}" \
|
|
904
|
+
-U "${_SMB_USER}%${_SMB_PASS}" \
|
|
905
|
+
-c "mkdir logs; put ${LOG_FILE} logs/${_LOG_BASENAME}" &>/dev/null 2>&1; then
|
|
906
|
+
spin_ok "Log pushed → //${_SMB_HOST}/${_SMB_SHARE}/logs/${_LOG_BASENAME}"
|
|
907
|
+
flog "INFO : Log uploaded to SMB //${_SMB_HOST}/${_SMB_SHARE}/logs/${_LOG_BASENAME}"
|
|
908
|
+
else
|
|
909
|
+
spin_warn "SMB upload failed — log saved locally only"
|
|
910
|
+
flog "WARN : SMB log upload failed"
|
|
911
|
+
fi
|
|
912
|
+
fi
|
|
913
|
+
|
|
914
|
+
echo ""
|
|
915
|
+
;;
|
|
916
|
+
|
|
917
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
918
|
+
# UNINSTALL — two modes: software-only or full drive wipe
|
|
581
919
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
582
920
|
uninstall)
|
|
583
921
|
PI_USER="${PI_USER:-pi}"
|
|
@@ -591,46 +929,83 @@ uninstall)
|
|
|
591
929
|
echo -e "${R} ║ PLC CHECK-WEIGHER UNINSTALLER ║${NC}"
|
|
592
930
|
echo -e "${R} ╚══════════════════════════════════════════════════════════╝${NC}"
|
|
593
931
|
echo ""
|
|
594
|
-
echo -e "
|
|
932
|
+
echo -e " Choose uninstall mode:"
|
|
595
933
|
echo ""
|
|
596
|
-
echo -e " ${
|
|
597
|
-
echo -e "
|
|
598
|
-
echo -e "
|
|
599
|
-
echo -e " ${R}✗${NC} CLI tool /usr/local/bin/plc_checkweigher"
|
|
600
|
-
echo -e " ${R}✗${NC} Plymouth theme saismruth (reverts to default)"
|
|
601
|
-
echo -e " ${R}✗${NC} RT kernel config (reverts /boot/firmware/config.txt)"
|
|
602
|
-
echo -e " ${R}✗${NC} LightDM drop-in /etc/systemd/system/lightdm.service.d/"
|
|
603
|
-
echo -e " ${R}✗${NC} NetworkManager cfg /etc/systemd/system/NetworkManager-wait-online.service.d/"
|
|
604
|
-
echo -e " ${R}✗${NC} Hotspot connection plc-hotspot (nmcli)"
|
|
605
|
-
echo -e " ${Y}!${NC} reports folder ${REPORTS_DIR} (you will be asked)"
|
|
934
|
+
echo -e " ${Y}1)${NC} ${W}Software only${NC}"
|
|
935
|
+
echo -e " ${D}Remove PLC services, code, venv and kernel config.${NC}"
|
|
936
|
+
echo -e " ${D}OS, user files and WiFi credentials are kept intact.${NC}"
|
|
606
937
|
echo ""
|
|
607
|
-
echo -e " ${
|
|
938
|
+
echo -e " ${R}2)${NC} ${W}Clean drive${NC} — prepare for fresh OS flash"
|
|
939
|
+
echo -e " ${D}Everything above PLUS all user data and WiFi credentials,${NC}"
|
|
940
|
+
echo -e " ${D}then zeros the entire SD card.${NC}"
|
|
941
|
+
echo -e " ${R} ⚠ The Pi will NOT boot after this. Reflash required.${NC}"
|
|
608
942
|
echo ""
|
|
609
|
-
|
|
943
|
+
|
|
944
|
+
while true; do
|
|
945
|
+
read -r -p " Mode [1 / 2]: " UNINSTALL_MODE </dev/tty
|
|
946
|
+
[[ "$UNINSTALL_MODE" == "1" || "$UNINSTALL_MODE" == "2" ]] && break
|
|
947
|
+
echo -e " ${R}Enter 1 or 2${NC}"
|
|
948
|
+
done
|
|
610
949
|
echo ""
|
|
611
950
|
|
|
612
|
-
|
|
613
|
-
|
|
951
|
+
# ── Mode-specific confirmation ────────────────────────────────────────────
|
|
952
|
+
KEEP_REPORTS="Y"
|
|
953
|
+
if [[ "$UNINSTALL_MODE" == "1" ]]; then
|
|
954
|
+
hr
|
|
955
|
+
echo ""
|
|
956
|
+
echo -e " ${R}✗${NC} systemd services plc_watcher plc_web"
|
|
957
|
+
echo -e " ${R}✗${NC} project code ${INSTALL_DIR}"
|
|
958
|
+
echo -e " ${R}✗${NC} Python venv ${VENV_DIR}"
|
|
959
|
+
echo -e " ${R}✗${NC} CLI tool plc_checkweigher"
|
|
960
|
+
echo -e " ${R}✗${NC} Plymouth theme saismruth → default"
|
|
961
|
+
echo -e " ${R}✗${NC} RT kernel config /boot/firmware/config.txt"
|
|
962
|
+
echo -e " ${Y}!${NC} reports folder ${REPORTS_DIR} (you will be asked)"
|
|
963
|
+
echo ""
|
|
964
|
+
echo -e " ${D}System packages (git, python3-venv, samba-client) NOT removed.${NC}"
|
|
965
|
+
echo ""
|
|
966
|
+
hr; echo ""
|
|
967
|
+
read -r -p " Type YES to confirm: " _CONFIRM </dev/tty
|
|
968
|
+
[[ "$_CONFIRM" == "YES" ]] || { echo " Aborted."; exit 0; }
|
|
969
|
+
echo ""
|
|
970
|
+
read -r -p " Keep report PDFs in ${REPORTS_DIR}? [Y/n]: " KEEP_REPORTS </dev/tty
|
|
971
|
+
KEEP_REPORTS="${KEEP_REPORTS:-Y}"
|
|
972
|
+
else
|
|
973
|
+
# Detect the drive that holds the root filesystem
|
|
974
|
+
_ROOT_SRC=$(findmnt -n -o SOURCE / 2>/dev/null || echo "")
|
|
975
|
+
_ROOT_DEV=$(lsblk -no pkname "$_ROOT_SRC" 2>/dev/null || echo "")
|
|
976
|
+
_ROOT_DEV_PATH="/dev/${_ROOT_DEV}"
|
|
614
977
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
978
|
+
hr; echo ""
|
|
979
|
+
echo -e " ${R}⚠ DESTRUCTIVE — READ CAREFULLY${NC}"
|
|
980
|
+
echo ""
|
|
981
|
+
echo -e " ${R}✗${NC} All PLC software, services and kernel config"
|
|
982
|
+
echo -e " ${R}✗${NC} All reports, logs and SMB credentials"
|
|
983
|
+
echo -e " ${R}✗${NC} SSH keys, bash history, npm cache, user configs"
|
|
984
|
+
echo -e " ${R}✗${NC} All saved WiFi connections"
|
|
985
|
+
echo -e " ${R}✗${NC} Entire drive zeroed: ${W}${_ROOT_DEV_PATH}${NC}"
|
|
986
|
+
echo ""
|
|
987
|
+
echo -e " ${R}The Pi will become unresponsive during the wipe.${NC}"
|
|
988
|
+
echo -e " ${D}Power off after ~2 minutes, then reflash with Raspberry Pi Imager.${NC}"
|
|
989
|
+
echo ""
|
|
990
|
+
hr; echo ""
|
|
991
|
+
read -r -p " Type WIPE to confirm drive wipe: " _CONFIRM </dev/tty
|
|
992
|
+
[[ "$_CONFIRM" == "WIPE" ]] || { echo " Aborted."; exit 0; }
|
|
993
|
+
fi
|
|
618
994
|
|
|
619
995
|
echo ""
|
|
620
996
|
need_sudo
|
|
621
997
|
|
|
622
|
-
# Step counter
|
|
623
|
-
|
|
998
|
+
# ── Step counter (9 for software-only, 12 for clean drive) ───────────────
|
|
999
|
+
_UT=$([[ "$UNINSTALL_MODE" == "1" ]] && echo "9" || echo "12")
|
|
1000
|
+
_US=0
|
|
624
1001
|
ustep() { _US=$((_US + 1)); spin_start "[${_US}/${_UT}] $*"; }
|
|
625
1002
|
|
|
626
1003
|
# ── 1. Stop and disable services ─────────────────────────────────────────
|
|
627
1004
|
echo ""
|
|
628
1005
|
ustep "Stopping and disabling services"
|
|
629
1006
|
for SVC in plc_watcher plc_web; do
|
|
630
|
-
systemctl is-active
|
|
631
|
-
|
|
632
|
-
systemctl is-enabled --quiet "$SVC" 2>/dev/null \
|
|
633
|
-
&& sudo systemctl disable "$SVC" 2>/dev/null || true
|
|
1007
|
+
systemctl is-active --quiet "$SVC" 2>/dev/null && sudo systemctl stop "$SVC" 2>/dev/null || true
|
|
1008
|
+
systemctl is-enabled --quiet "$SVC" 2>/dev/null && sudo systemctl disable "$SVC" 2>/dev/null || true
|
|
634
1009
|
done
|
|
635
1010
|
sudo rm -f /etc/systemd/system/plc_watcher.service \
|
|
636
1011
|
/etc/systemd/system/plc_web.service
|
|
@@ -657,27 +1032,23 @@ uninstall)
|
|
|
657
1032
|
fi
|
|
658
1033
|
spin_ok "Reverted to '${DEFAULT_THEME:-default}'"
|
|
659
1034
|
|
|
660
|
-
# ── 4. Rebuild initramfs
|
|
1035
|
+
# ── 4. Rebuild initramfs ─────────────────────────────────────────────────
|
|
661
1036
|
ustep "Rebuilding initramfs ${D}(~30 s)${NC}"
|
|
662
1037
|
sudo update-initramfs -u > /tmp/uninstall_initramfs.log 2>&1 \
|
|
663
|
-
&& spin_ok
|
|
664
|
-
|| spin_warn "Warnings — see /tmp/uninstall_initramfs.log"
|
|
1038
|
+
&& spin_ok || spin_warn "Warnings — see /tmp/uninstall_initramfs.log"
|
|
665
1039
|
|
|
666
1040
|
# ── 5. RT kernel revert ──────────────────────────────────────────────────
|
|
667
1041
|
ustep "Reverting RT kernel config"
|
|
668
1042
|
if [[ -f "${BOOT_FW}/config.txt" ]]; then
|
|
669
|
-
sudo sed -i '/### PLC-RT-BLOCK-START ###/,/### PLC-RT-BLOCK-END ###/d'
|
|
670
|
-
"${BOOT_FW}/config.txt"
|
|
1043
|
+
sudo sed -i '/### PLC-RT-BLOCK-START ###/,/### PLC-RT-BLOCK-END ###/d' "${BOOT_FW}/config.txt"
|
|
671
1044
|
sudo sed -i '/^gpu_mem=128$/d' "${BOOT_FW}/config.txt"
|
|
672
|
-
sudo rm -f "${BOOT_FW}/kernel8-rt.img"
|
|
673
|
-
|
|
674
|
-
"${BOOT_FW}/kernel8-stock.img"
|
|
675
|
-
spin_ok "Stock kernel will boot after reboot"
|
|
1045
|
+
sudo rm -f "${BOOT_FW}/kernel8-rt.img" "${BOOT_FW}/initramfs8-rt" "${BOOT_FW}/kernel8-stock.img"
|
|
1046
|
+
spin_ok "Stock kernel restored"
|
|
676
1047
|
else
|
|
677
1048
|
spin_warn "config.txt not found — skipped"
|
|
678
1049
|
fi
|
|
679
1050
|
|
|
680
|
-
# ── 6.
|
|
1051
|
+
# ── 6. Network cleanup ───────────────────────────────────────────────────
|
|
681
1052
|
ustep "Cleaning up network connections"
|
|
682
1053
|
sudo nmcli connection delete "plc-hotspot" 2>/dev/null || true
|
|
683
1054
|
sudo systemctl daemon-reload
|
|
@@ -685,15 +1056,10 @@ uninstall)
|
|
|
685
1056
|
|
|
686
1057
|
# ── 7. Python venv ───────────────────────────────────────────────────────
|
|
687
1058
|
ustep "Removing Python environment"
|
|
688
|
-
|
|
689
|
-
rm -rf "$VENV_DIR"
|
|
690
|
-
spin_ok "Removed ${VENV_DIR}"
|
|
691
|
-
else
|
|
692
|
-
spin_ok "Already gone"
|
|
693
|
-
fi
|
|
1059
|
+
[[ -d "$VENV_DIR" ]] && rm -rf "$VENV_DIR" && spin_ok "Removed ${VENV_DIR}" || spin_ok "Already gone"
|
|
694
1060
|
|
|
695
|
-
# ── 8.
|
|
696
|
-
ustep "Cleaning up runtime files"
|
|
1061
|
+
# ── 8. Runtime files + CLI ───────────────────────────────────────────────
|
|
1062
|
+
ustep "Cleaning up runtime files and CLI"
|
|
697
1063
|
rm -f /tmp/plc_live.json 2>/dev/null || true
|
|
698
1064
|
BASHRC="${HOME_DIR}/.bashrc"
|
|
699
1065
|
[[ -f "$BASHRC" ]] && sed -i '/export PATH.*\.local\/bin/d' "$BASHRC" 2>/dev/null || true
|
|
@@ -704,16 +1070,61 @@ uninstall)
|
|
|
704
1070
|
fi
|
|
705
1071
|
spin_ok "CLI and temp files removed"
|
|
706
1072
|
|
|
707
|
-
# ── 9.
|
|
1073
|
+
# ── 9. Project code ──────────────────────────────────────────────────────
|
|
708
1074
|
ustep "Removing project code"
|
|
709
1075
|
if [[ -d "$INSTALL_DIR" ]]; then
|
|
710
|
-
rm -rf "$INSTALL_DIR"
|
|
711
|
-
spin_ok "Removed ${INSTALL_DIR}"
|
|
1076
|
+
sudo rm -rf "$INSTALL_DIR" && spin_ok "Removed ${INSTALL_DIR}"
|
|
712
1077
|
else
|
|
713
1078
|
spin_ok "Already gone"
|
|
714
1079
|
fi
|
|
715
1080
|
|
|
716
|
-
#
|
|
1081
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
1082
|
+
# MODE 2 ONLY: wipe all user data then zero the drive
|
|
1083
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
1084
|
+
if [[ "$UNINSTALL_MODE" == "2" ]]; then
|
|
1085
|
+
|
|
1086
|
+
# ── 10. All WiFi connections ─────────────────────────────────────────
|
|
1087
|
+
ustep "Removing all WiFi connections"
|
|
1088
|
+
nmcli -t -f NAME con show 2>/dev/null | while IFS= read -r _CON; do
|
|
1089
|
+
[[ -n "$_CON" ]] && sudo nmcli connection delete "$_CON" 2>/dev/null || true
|
|
1090
|
+
done
|
|
1091
|
+
spin_ok "All network connections cleared"
|
|
1092
|
+
|
|
1093
|
+
# ── 11. User data ────────────────────────────────────────────────────
|
|
1094
|
+
ustep "Wiping user data"
|
|
1095
|
+
rm -rf "${REPORTS_DIR}" 2>/dev/null || true
|
|
1096
|
+
rm -rf "${HOME_DIR}/.ssh" 2>/dev/null || true
|
|
1097
|
+
rm -f "${HOME_DIR}/.bash_history" 2>/dev/null || true
|
|
1098
|
+
rm -rf "${HOME_DIR}/.npm" 2>/dev/null || true
|
|
1099
|
+
rm -rf "${HOME_DIR}/.config" 2>/dev/null || true
|
|
1100
|
+
rm -rf "${HOME_DIR}/.cache" 2>/dev/null || true
|
|
1101
|
+
rm -rf "${HOME_DIR}/.local" 2>/dev/null || true
|
|
1102
|
+
spin_ok "User data cleared"
|
|
1103
|
+
|
|
1104
|
+
# ── 12. Zero the drive ───────────────────────────────────────────────
|
|
1105
|
+
ustep "Zeroing drive — ${_ROOT_DEV_PATH}"
|
|
1106
|
+
_spin_kill
|
|
1107
|
+
echo ""
|
|
1108
|
+
echo -e " ${R}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
1109
|
+
echo -e " ${R} Starting drive wipe. The system will become unresponsive.${NC}"
|
|
1110
|
+
echo -e " ${R} Power off after 2 minutes and reflash with Pi Imager.${NC}"
|
|
1111
|
+
echo -e " ${R}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
1112
|
+
echo ""
|
|
1113
|
+
for _i in 10 9 8 7 6 5 4 3 2 1; do
|
|
1114
|
+
printf "\r Wiping in %2d s ... (Ctrl-C to abort) " "$_i"
|
|
1115
|
+
sleep 1
|
|
1116
|
+
done
|
|
1117
|
+
printf '\r\033[K'
|
|
1118
|
+
echo -e " ${R}Wiping ${_ROOT_DEV_PATH} ...${NC}"
|
|
1119
|
+
echo ""
|
|
1120
|
+
sudo dd if=/dev/zero of="${_ROOT_DEV_PATH}" bs=4M status=progress 2>&1 || true
|
|
1121
|
+
# The system will have crashed by here. This line is a safety net.
|
|
1122
|
+
echo ""
|
|
1123
|
+
warn "Wipe complete — power off and reflash."
|
|
1124
|
+
exit 0
|
|
1125
|
+
fi
|
|
1126
|
+
|
|
1127
|
+
# ── Done (mode 1 only) ────────────────────────────────────────────────────
|
|
717
1128
|
echo ""
|
|
718
1129
|
echo -e "${G}"
|
|
719
1130
|
echo " ╔══════════════════════════════════════════════════════════╗"
|
|
@@ -724,13 +1135,10 @@ uninstall)
|
|
|
724
1135
|
echo -e "${NC}"
|
|
725
1136
|
[[ "${KEEP_REPORTS^^}" == "Y" ]] && info "PDFs still at: ${REPORTS_DIR} (remove manually if needed)"
|
|
726
1137
|
echo ""
|
|
727
|
-
|
|
728
1138
|
read -r -p " Reboot now? [Y/n]: " DO_REBOOT </dev/tty
|
|
729
1139
|
DO_REBOOT="${DO_REBOOT:-Y}"
|
|
730
1140
|
if [[ "${DO_REBOOT^^}" == "Y" ]]; then
|
|
731
|
-
spin_start "Rebooting"
|
|
732
|
-
sleep 1
|
|
733
|
-
sudo reboot
|
|
1141
|
+
spin_start "Rebooting"; sleep 1; sudo reboot
|
|
734
1142
|
else
|
|
735
1143
|
warn "Remember to reboot for kernel changes to take effect."
|
|
736
1144
|
echo ""
|
|
@@ -745,7 +1153,11 @@ help|--help|-h)
|
|
|
745
1153
|
echo -e "${B} plc_checkweigher${NC} — PLC Check-Weigher system CLI"
|
|
746
1154
|
echo ""
|
|
747
1155
|
echo -e " ${W}Diagnostics${NC}"
|
|
748
|
-
echo " status Full system diagnostic
|
|
1156
|
+
echo " status Full system diagnostic (auto-runs fix on errors)"
|
|
1157
|
+
echo " fix Auto-detect and repair all issues + write log"
|
|
1158
|
+
echo " fix -wifi Fix WiFi connectivity only"
|
|
1159
|
+
echo " fix -health Fix disk, memory, time, dirs"
|
|
1160
|
+
echo " fix -programs Fix services, packages, queue, unit files"
|
|
749
1161
|
echo " logs Stream live logs (plc_watcher + plc_web)"
|
|
750
1162
|
echo " queue Show SMB pending queue and delivery ledger"
|
|
751
1163
|
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plc-checkweigher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"description": "One-command installer for the PLC Check-Weigher system on Raspberry Pi (PREEMPT_RT kernel, Python stack, WiFi, SMB, systemd RT services)",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"postinstall": "node bin/cli.js"
|
package/setup.sh
CHANGED
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
# 3. Clone / update repo
|
|
13
13
|
# 4. Python venv + pip install
|
|
14
14
|
# 5. Create /home/<user>/reports
|
|
15
|
-
# 6. WiFi
|
|
16
|
-
# 7. SMB
|
|
15
|
+
# 6. WiFi — scan → pick from list → password
|
|
16
|
+
# 7. SMB — enter host IP, share name, credentials → smb_config.py
|
|
17
17
|
# 8. NetworkManager-wait-online
|
|
18
18
|
# 9. systemd services (plc_watcher + plc_web)
|
|
19
|
-
# 10. Boot logo
|
|
20
|
-
# 11. Display
|
|
19
|
+
# 10. Boot logo — Plymouth theme with logo.png + "SAI SAMARTH ENGG"
|
|
20
|
+
# 11. Display — LightDM priority, CPU isolation, utmpx
|
|
21
|
+
# 11b. VS Code — priority daemon: cores 0-2, Nice=-5
|
|
21
22
|
# 12. PREEMPT_RT kernel ← installed last so only one reboot is needed
|
|
22
23
|
# 13. REBOOT
|
|
23
24
|
# =============================================================================
|
|
@@ -31,6 +32,7 @@ REPO_URL="https://github.com/Bibin-VR/plc-checkweigher.git"
|
|
|
31
32
|
REPO_BRANCH="main"
|
|
32
33
|
HOME_DIR="/home/${PI_USER}"
|
|
33
34
|
INSTALL_DIR="${HOME_DIR}/plc_checkweigher"
|
|
35
|
+
DATA_DIR="${INSTALL_DIR}/data" # pi-writable: queue, log, smb_config
|
|
34
36
|
VENV_DIR="${HOME_DIR}/plc_env"
|
|
35
37
|
REPORTS_DIR="${HOME_DIR}/reports"
|
|
36
38
|
BOOT_FW="/boot/firmware"
|
|
@@ -106,15 +108,20 @@ install_system_packages() {
|
|
|
106
108
|
# ── 2. Clone / update repo ────────────────────────────────────────────────────
|
|
107
109
|
setup_repo() {
|
|
108
110
|
step "Repository ..."
|
|
111
|
+
# Mark safe so git operations work regardless of ownership state.
|
|
112
|
+
git config --global --add safe.directory "${INSTALL_DIR}" 2>/dev/null || true
|
|
113
|
+
|
|
109
114
|
if [[ -d "${INSTALL_DIR}/.git" ]]; then
|
|
110
|
-
|
|
115
|
+
# Temporarily unlock so git can write index / pack files.
|
|
116
|
+
chown -R root:root "${INSTALL_DIR}" 2>/dev/null || true
|
|
117
|
+
git -C "${INSTALL_DIR}" pull --ff-only origin "${REPO_BRANCH}" \
|
|
111
118
|
&& ok "Repo updated → ${INSTALL_DIR}" \
|
|
112
119
|
|| warn "git pull failed — using existing files"
|
|
113
120
|
else
|
|
114
|
-
|
|
115
|
-
"${REPO_URL}" "${INSTALL_DIR}"
|
|
121
|
+
git clone --branch "${REPO_BRANCH}" "${REPO_URL}" "${INSTALL_DIR}"
|
|
116
122
|
ok "Repo cloned → ${INSTALL_DIR}"
|
|
117
123
|
fi
|
|
124
|
+
# Permissions are finalised by lock_source_files() later.
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
# ── 3. Python venv ────────────────────────────────────────────────────────────
|
|
@@ -134,6 +141,13 @@ setup_dirs() {
|
|
|
134
141
|
mkdir -p "${REPORTS_DIR}"
|
|
135
142
|
chown "${PI_USER}:${PI_USER}" "${REPORTS_DIR}"
|
|
136
143
|
ok "${REPORTS_DIR}"
|
|
144
|
+
|
|
145
|
+
# pi-writable data directory — queue, delivery log, SMB credentials live here.
|
|
146
|
+
# Source files above this directory are root-locked after install_services().
|
|
147
|
+
mkdir -p "${DATA_DIR}"
|
|
148
|
+
chown "${PI_USER}:${PI_USER}" "${DATA_DIR}"
|
|
149
|
+
chmod 755 "${DATA_DIR}"
|
|
150
|
+
ok "${DATA_DIR} (runtime data — pi-writable)"
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
# ── CLI tool — install plc_checkweigher command ───────────────────────────────
|
|
@@ -252,8 +266,8 @@ setup_smb() {
|
|
|
252
266
|
prompt SMB_HOST "Host IP address" ""
|
|
253
267
|
if [[ -z "${SMB_HOST}" ]]; then
|
|
254
268
|
warn "SMB push disabled — no host entered."
|
|
255
|
-
# Write a disabled smb_config.py
|
|
256
|
-
cat > "${
|
|
269
|
+
# Write a disabled smb_config.py to the pi-writable data/ directory.
|
|
270
|
+
cat > "${DATA_DIR}/smb_config.py" << 'EOF'
|
|
257
271
|
# SMB push disabled during setup
|
|
258
272
|
SMB_ENABLED = False
|
|
259
273
|
SMB_HOST = ""
|
|
@@ -262,7 +276,7 @@ SMB_USERNAME = ""
|
|
|
262
276
|
SMB_PASSWORD = ""
|
|
263
277
|
SMB_SUBDIR = ""
|
|
264
278
|
EOF
|
|
265
|
-
chown "${PI_USER}:${PI_USER}" "${
|
|
279
|
+
chown "${PI_USER}:${PI_USER}" "${DATA_DIR}/smb_config.py"
|
|
266
280
|
return
|
|
267
281
|
fi
|
|
268
282
|
|
|
@@ -274,8 +288,9 @@ EOF
|
|
|
274
288
|
hr
|
|
275
289
|
echo ""
|
|
276
290
|
|
|
277
|
-
# Write smb_config.py (gitignored — credentials stay off GitHub)
|
|
278
|
-
|
|
291
|
+
# Write smb_config.py to data/ (gitignored — credentials stay off GitHub).
|
|
292
|
+
# Stored in data/ so the pi user can update it via: plc_checkweigher smb-config
|
|
293
|
+
cat > "${DATA_DIR}/smb_config.py" << EOF
|
|
279
294
|
# SMB configuration — written by setup.sh, NOT committed to git.
|
|
280
295
|
SMB_ENABLED = True
|
|
281
296
|
SMB_HOST = "${SMB_HOST}"
|
|
@@ -284,8 +299,8 @@ SMB_USERNAME = "${SMB_USERNAME}"
|
|
|
284
299
|
SMB_PASSWORD = "${SMB_PASSWORD}"
|
|
285
300
|
SMB_SUBDIR = "${SMB_SUBDIR}"
|
|
286
301
|
EOF
|
|
287
|
-
chown "${PI_USER}:${PI_USER}" "${
|
|
288
|
-
ok "SMB config saved → ${
|
|
302
|
+
chown "${PI_USER}:${PI_USER}" "${DATA_DIR}/smb_config.py"
|
|
303
|
+
ok "SMB config saved → ${DATA_DIR}/smb_config.py"
|
|
289
304
|
|
|
290
305
|
# Test connectivity
|
|
291
306
|
echo -n " Testing connection to ${SMB_HOST} ..."
|
|
@@ -478,13 +493,13 @@ setup_display() {
|
|
|
478
493
|
# Start after hardware udev settles (HDMI/DSI detected) — not after network.
|
|
479
494
|
After=systemd-udev-settle.service local-fs.target acpid.socket dbus.service
|
|
480
495
|
Wants=systemd-udev-settle.service
|
|
496
|
+
# StartLimit* MUST be in [Unit] — ignored in [Service].
|
|
497
|
+
StartLimitBurst=10
|
|
498
|
+
StartLimitIntervalSec=60
|
|
481
499
|
|
|
482
500
|
[Service]
|
|
483
|
-
# Generous restart policy — display should always recover.
|
|
484
|
-
StartLimitBurst=20
|
|
485
|
-
StartLimitIntervalSec=120
|
|
486
501
|
Restart=on-failure
|
|
487
|
-
RestartSec=
|
|
502
|
+
RestartSec=5
|
|
488
503
|
|
|
489
504
|
# CPU cores 0-2 only — core 3 is reserved for SCHED_FIFO PLC process.
|
|
490
505
|
CPUAffinity=0 1 2
|
|
@@ -494,7 +509,7 @@ Nice=-5
|
|
|
494
509
|
|
|
495
510
|
LimitNOFILE=65536
|
|
496
511
|
EOF
|
|
497
|
-
ok "LightDM: CPUAffinity=0-2, Nice=-5,
|
|
512
|
+
ok "LightDM: CPUAffinity=0-2, Nice=-5, StartLimitBurst=10 in [Unit]"
|
|
498
513
|
|
|
499
514
|
# ── Fix utmpx — PAM needs /run/utmp to track sessions ───────────────────
|
|
500
515
|
cat > /etc/tmpfiles.d/utmp-fix.conf << 'EOF'
|
|
@@ -503,18 +518,127 @@ EOF
|
|
|
503
518
|
systemd-tmpfiles --create /etc/tmpfiles.d/utmp-fix.conf 2>/dev/null || true
|
|
504
519
|
ok "/run/utmp fixed (utmpx PAM session tracking)"
|
|
505
520
|
|
|
506
|
-
# ──
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
521
|
+
# ── Fix vc4-kms display driver — PREEMPT_RT EPROBE_DEFER workaround ─────
|
|
522
|
+
# On the RT kernel, vc4_hdmi defers its probe indefinitely waiting for the
|
|
523
|
+
# PCM audio component (-517 = EPROBE_DEFER). noaudio removes that dependency
|
|
524
|
+
# so the display DRM card is created on first probe attempt.
|
|
525
|
+
# hdmi_force_hotplug=1 initialises HDMI hardware even when no display is
|
|
526
|
+
# connected at boot (required for headless + Pi Connect screen sharing).
|
|
527
|
+
sed -i 's/^dtoverlay=vc4-kms-v3d$/dtoverlay=vc4-kms-v3d,noaudio/' \
|
|
528
|
+
"${BOOT_FW}/config.txt" 2>/dev/null || true
|
|
529
|
+
if ! grep -q '^hdmi_force_hotplug' "${BOOT_FW}/config.txt"; then
|
|
530
|
+
if grep -q '^\[all\]' "${BOOT_FW}/config.txt"; then
|
|
531
|
+
sed -i '/^\[all\]/a hdmi_force_hotplug=1' "${BOOT_FW}/config.txt"
|
|
532
|
+
else
|
|
533
|
+
echo "hdmi_force_hotplug=1" >> "${BOOT_FW}/config.txt"
|
|
534
|
+
fi
|
|
510
535
|
else
|
|
511
|
-
|
|
536
|
+
sed -i 's/^hdmi_force_hotplug=.*/hdmi_force_hotplug=1/' "${BOOT_FW}/config.txt"
|
|
512
537
|
fi
|
|
513
|
-
ok "
|
|
538
|
+
ok "vc4-kms-v3d,noaudio + hdmi_force_hotplug=1 (HDMI always initialised)"
|
|
539
|
+
|
|
540
|
+
# ── Enable rpi-connect user service (Pi Connect remote access) ──────────
|
|
541
|
+
loginctl enable-linger "${PI_USER}" 2>/dev/null || true
|
|
542
|
+
sudo -u "${PI_USER}" systemctl --user enable rpi-connect.service 2>/dev/null || true
|
|
543
|
+
ok "rpi-connect user service enabled (sign in with: rpi-connect signin)"
|
|
514
544
|
|
|
515
545
|
systemctl daemon-reload
|
|
516
546
|
systemctl enable lightdm.service 2>/dev/null || true
|
|
517
|
-
ok "LightDM enabled — starts on every boot
|
|
547
|
+
ok "LightDM enabled — starts on every boot"
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
# ── 11b. VS Code server priority ──────────────────────────────────────────────
|
|
551
|
+
setup_vscode_priority() {
|
|
552
|
+
step "VS Code server priority ..."
|
|
553
|
+
|
|
554
|
+
cat > /usr/local/bin/vscode-priority-daemon << 'DAEMON'
|
|
555
|
+
#!/usr/bin/env bash
|
|
556
|
+
# Apply CPU affinity (cores 0-2) and Nice=-5 to VS Code server processes.
|
|
557
|
+
# Core 3 is reserved exclusively for the SCHED_FIFO PLC process.
|
|
558
|
+
# Runs every 60s so newly-spawned extension host processes are caught promptly.
|
|
559
|
+
while true; do
|
|
560
|
+
mapfile -t pids < <(pgrep -u pi -f '\.vscode-server' 2>/dev/null || true)
|
|
561
|
+
for pid in "${pids[@]}"; do
|
|
562
|
+
taskset -cp 0-2 "$pid" >/dev/null 2>&1 || true
|
|
563
|
+
renice -n -5 -p "$pid" >/dev/null 2>&1 || true
|
|
564
|
+
done
|
|
565
|
+
sleep 60
|
|
566
|
+
done
|
|
567
|
+
DAEMON
|
|
568
|
+
chmod +x /usr/local/bin/vscode-priority-daemon
|
|
569
|
+
|
|
570
|
+
cat > /etc/systemd/system/vscode-priority.service << 'EOF'
|
|
571
|
+
[Unit]
|
|
572
|
+
Description=VS Code Server priority manager (cores 0-2, Nice=-5)
|
|
573
|
+
After=multi-user.target
|
|
574
|
+
Wants=multi-user.target
|
|
575
|
+
|
|
576
|
+
[Service]
|
|
577
|
+
Type=simple
|
|
578
|
+
ExecStart=/usr/local/bin/vscode-priority-daemon
|
|
579
|
+
Restart=always
|
|
580
|
+
RestartSec=10
|
|
581
|
+
User=root
|
|
582
|
+
|
|
583
|
+
[Install]
|
|
584
|
+
WantedBy=multi-user.target
|
|
585
|
+
EOF
|
|
586
|
+
|
|
587
|
+
systemctl daemon-reload
|
|
588
|
+
systemctl enable vscode-priority.service
|
|
589
|
+
ok "vscode-priority.service (cores 0-2, Nice=-5) — starts on every boot"
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
# ── 11c. Lock source files — root:root 644 (requires sudo to edit) ────────────
|
|
593
|
+
lock_source_files() {
|
|
594
|
+
step "Protecting source files ..."
|
|
595
|
+
|
|
596
|
+
# Source files: root:root 644 — readable+executable by all, editable by root only.
|
|
597
|
+
find "${INSTALL_DIR}" -maxdepth 1 -name "*.py" -exec chown root:root {} \; \
|
|
598
|
+
-exec chmod 644 {} \;
|
|
599
|
+
find "${INSTALL_DIR}/web" -name "*.py" -exec chown root:root {} \; \
|
|
600
|
+
-exec chmod 644 {} \; 2>/dev/null || true
|
|
601
|
+
find "${INSTALL_DIR}/web/templates" -name "*.html" -exec chown root:root {} \; \
|
|
602
|
+
-exec chmod 644 {} \; 2>/dev/null || true
|
|
603
|
+
find "${INSTALL_DIR}/web/static" -type f -exec chown root:root {} \; \
|
|
604
|
+
-exec chmod 644 {} \; 2>/dev/null || true
|
|
605
|
+
|
|
606
|
+
# Directories: root:root 755 — pi can list/cd but cannot create new files.
|
|
607
|
+
chown root:root "${INSTALL_DIR}"
|
|
608
|
+
chmod 755 "${INSTALL_DIR}"
|
|
609
|
+
[[ -d "${INSTALL_DIR}/web" ]] && chown root:root "${INSTALL_DIR}/web" && chmod 755 "${INSTALL_DIR}/web"
|
|
610
|
+
[[ -d "${INSTALL_DIR}/web/templates" ]] && chown root:root "${INSTALL_DIR}/web/templates" && chmod 755 "${INSTALL_DIR}/web/templates"
|
|
611
|
+
[[ -d "${INSTALL_DIR}/web/static" ]] && chown root:root "${INSTALL_DIR}/web/static" && chmod 755 "${INSTALL_DIR}/web/static"
|
|
612
|
+
[[ -d "${INSTALL_DIR}/assets" ]] && chown root:root "${INSTALL_DIR}/assets" && chmod 755 "${INSTALL_DIR}/assets"
|
|
613
|
+
[[ -d "${INSTALL_DIR}/bin" ]] && chown root:root "${INSTALL_DIR}/bin" && chmod 755 "${INSTALL_DIR}/bin"
|
|
614
|
+
|
|
615
|
+
# service files and scripts: root:root, readable
|
|
616
|
+
find "${INSTALL_DIR}" -maxdepth 2 -name "*.service" -exec chown root:root {} \; \
|
|
617
|
+
-exec chmod 644 {} \; 2>/dev/null || true
|
|
618
|
+
find "${INSTALL_DIR}" -maxdepth 2 -name "*.sh" -exec chown root:root {} \; \
|
|
619
|
+
-exec chmod 755 {} \; 2>/dev/null || true
|
|
620
|
+
|
|
621
|
+
# Data directory: pi-owned 755 — services write queue, log, smb_config here.
|
|
622
|
+
chown "${PI_USER}:${PI_USER}" "${DATA_DIR}"
|
|
623
|
+
chmod 755 "${DATA_DIR}"
|
|
624
|
+
|
|
625
|
+
# Pre-create data files with correct ownership so pi can write on first run.
|
|
626
|
+
local queue="${DATA_DIR}/delivery_queue.json"
|
|
627
|
+
local log="${DATA_DIR}/delivery_sent.log"
|
|
628
|
+
[[ -f "${queue}" ]] || echo '[]' > "${queue}"
|
|
629
|
+
[[ -f "${log}" ]] || touch "${log}"
|
|
630
|
+
chown "${PI_USER}:${PI_USER}" "${queue}" "${log}"
|
|
631
|
+
chmod 644 "${queue}" "${log}"
|
|
632
|
+
|
|
633
|
+
# smb_config.py in data/ stays pi-owned so plc_checkweigher smb-config can update it.
|
|
634
|
+
[[ -f "${DATA_DIR}/smb_config.py" ]] && \
|
|
635
|
+
chown "${PI_USER}:${PI_USER}" "${DATA_DIR}/smb_config.py" && \
|
|
636
|
+
chmod 644 "${DATA_DIR}/smb_config.py"
|
|
637
|
+
|
|
638
|
+
ok "Source files locked (root:root 644 — sudo required to edit)"
|
|
639
|
+
ok "Data dir writable ${DATA_DIR} (queue / log / smb_config)"
|
|
640
|
+
info "To update source: sudo nano ${INSTALL_DIR}/plc_reader.py"
|
|
641
|
+
info "To update SMB: plc_checkweigher smb-config (no sudo needed)"
|
|
518
642
|
}
|
|
519
643
|
|
|
520
644
|
# ── 12. RT kernel — installed LAST so only one reboot is needed ───────────────
|
|
@@ -576,10 +700,11 @@ do_reboot() {
|
|
|
576
700
|
banner "Setup Complete"
|
|
577
701
|
echo ""
|
|
578
702
|
PI_IP="$(hostname -I | awk '{print $1}' 2>/dev/null || echo '<pi-ip>')"
|
|
579
|
-
printf " ${G}%-32s${NC} %s\n" "
|
|
703
|
+
printf " ${G}%-32s${NC} %s\n" "Source (root-locked):" "${INSTALL_DIR}"
|
|
704
|
+
printf " ${G}%-32s${NC} %s\n" "Data (pi-writable):" "${DATA_DIR}"
|
|
580
705
|
printf " ${G}%-32s${NC} %s\n" "Python venv:" "${VENV_DIR}"
|
|
581
706
|
printf " ${G}%-32s${NC} %s\n" "Reports output:" "${REPORTS_DIR}"
|
|
582
|
-
printf " ${G}%-32s${NC} %s\n" "SMB config:" "${
|
|
707
|
+
printf " ${G}%-32s${NC} %s\n" "SMB config:" "${DATA_DIR}/smb_config.py"
|
|
583
708
|
printf " ${G}%-32s${NC} %s\n" "RT kernel:" "kernel8-rt.img (active after reboot)"
|
|
584
709
|
printf " ${G}%-32s${NC} %s\n" "Stock kernel fallback:" "kernel8-stock.img"
|
|
585
710
|
echo ""
|
|
@@ -621,9 +746,11 @@ main() {
|
|
|
621
746
|
setup_smb # 7 — interactive SMB config → smb_config.py
|
|
622
747
|
setup_network_online # 8
|
|
623
748
|
install_services # 9
|
|
624
|
-
setup_boot_logo #
|
|
625
|
-
setup_display #
|
|
626
|
-
|
|
749
|
+
setup_boot_logo # 10 — Plymouth: logo + "SAI SAMARTH ENGG"
|
|
750
|
+
setup_display # 11 — LightDM priority, CPU isolation, utmpx
|
|
751
|
+
setup_vscode_priority # 11b — VS Code: cores 0-2, Nice=-5
|
|
752
|
+
lock_source_files # 11c — root:root on .py, pi:pi on data/
|
|
753
|
+
install_rt_kernel # 12 — LAST, so only one reboot needed
|
|
627
754
|
do_reboot # 12 — single reboot applies everything
|
|
628
755
|
}
|
|
629
756
|
|