plc-checkweigher 1.17.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.
@@ -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
- SMB_CFG="${INSTALL_DIR}/smb_config.py"
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
@@ -210,8 +211,8 @@ stop)
210
211
  queue)
211
212
  banner "SMB Delivery Queue"
212
213
  echo ""
213
- QUEUE="${INSTALL_DIR}/delivery_queue.json"
214
- LEDGER="${INSTALL_DIR}/delivery_sent.log"
214
+ QUEUE="${DATA_DIR}/delivery_queue.json"
215
+ LEDGER="${DATA_DIR}/delivery_sent.log"
215
216
  if [[ -f "$QUEUE" ]]; then
216
217
  COUNT=$(python3 -c "import json; d=json.load(open('$QUEUE')); print(len(d))" 2>/dev/null || echo 0)
217
218
  if [[ "$COUNT" -eq 0 ]]; then
@@ -772,9 +773,17 @@ fix)
772
773
  fi
773
774
 
774
775
  spin_start "SMB config file"
776
+ mkdir -p "${DATA_DIR}" 2>/dev/null || true
775
777
  if [[ ! -f "$SMB_CFG" ]]; then
776
778
  printf 'SMB_ENABLED = False\nSMB_HOST = ""\nSMB_SHARE = "Reports"\nSMB_USERNAME = ""\nSMB_PASSWORD = ""\nSMB_SUBDIR = ""\n' \
777
- > "$SMB_CFG" 2>/dev/null
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
778
787
  ffix_ok "Created default smb_config.py (disabled — run: plc_checkweigher smb-config)"
779
788
  else
780
789
  ffix_info "smb_config.py present"
@@ -836,13 +845,14 @@ fix)
836
845
  fi
837
846
 
838
847
  spin_start "Delivery queue"
839
- _QUEUE="${INSTALL_DIR}/delivery_queue.json"
848
+ _QUEUE="${DATA_DIR}/delivery_queue.json"
849
+ mkdir -p "${DATA_DIR}" 2>/dev/null || true
840
850
  if [[ ! -f "$_QUEUE" ]]; then
841
- echo "[]" > "$_QUEUE"
851
+ echo "[]" > "$_QUEUE" 2>/dev/null || true
842
852
  ffix_ok "Created missing delivery_queue.json"
843
853
  elif ! "${PYTHON}" -c "import json; json.load(open('${_QUEUE}'))" &>/dev/null 2>&1; then
844
854
  mv "$_QUEUE" "${_QUEUE}.broken.$(date +%s)" 2>/dev/null || true
845
- echo "[]" > "$_QUEUE"
855
+ echo "[]" > "$_QUEUE" 2>/dev/null || true
846
856
  ffix_ok "Corrupt delivery queue reset (backup saved)"
847
857
  else
848
858
  ffix_info "delivery_queue.json valid"
@@ -1063,7 +1073,7 @@ uninstall)
1063
1073
  # ── 9. Project code ──────────────────────────────────────────────────────
1064
1074
  ustep "Removing project code"
1065
1075
  if [[ -d "$INSTALL_DIR" ]]; then
1066
- rm -rf "$INSTALL_DIR" && spin_ok "Removed ${INSTALL_DIR}"
1076
+ sudo rm -rf "$INSTALL_DIR" && spin_ok "Removed ${INSTALL_DIR}"
1067
1077
  else
1068
1078
  spin_ok "Already gone"
1069
1079
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plc-checkweigher",
3
- "version": "1.17.0",
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
@@ -32,6 +32,7 @@ REPO_URL="https://github.com/Bibin-VR/plc-checkweigher.git"
32
32
  REPO_BRANCH="main"
33
33
  HOME_DIR="/home/${PI_USER}"
34
34
  INSTALL_DIR="${HOME_DIR}/plc_checkweigher"
35
+ DATA_DIR="${INSTALL_DIR}/data" # pi-writable: queue, log, smb_config
35
36
  VENV_DIR="${HOME_DIR}/plc_env"
36
37
  REPORTS_DIR="${HOME_DIR}/reports"
37
38
  BOOT_FW="/boot/firmware"
@@ -107,15 +108,20 @@ install_system_packages() {
107
108
  # ── 2. Clone / update repo ────────────────────────────────────────────────────
108
109
  setup_repo() {
109
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
+
110
114
  if [[ -d "${INSTALL_DIR}/.git" ]]; then
111
- sudo -u "${PI_USER}" git -C "${INSTALL_DIR}" pull --ff-only origin "${REPO_BRANCH}" \
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}" \
112
118
  && ok "Repo updated → ${INSTALL_DIR}" \
113
119
  || warn "git pull failed — using existing files"
114
120
  else
115
- sudo -u "${PI_USER}" git clone --branch "${REPO_BRANCH}" \
116
- "${REPO_URL}" "${INSTALL_DIR}"
121
+ git clone --branch "${REPO_BRANCH}" "${REPO_URL}" "${INSTALL_DIR}"
117
122
  ok "Repo cloned → ${INSTALL_DIR}"
118
123
  fi
124
+ # Permissions are finalised by lock_source_files() later.
119
125
  }
120
126
 
121
127
  # ── 3. Python venv ────────────────────────────────────────────────────────────
@@ -135,6 +141,13 @@ setup_dirs() {
135
141
  mkdir -p "${REPORTS_DIR}"
136
142
  chown "${PI_USER}:${PI_USER}" "${REPORTS_DIR}"
137
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)"
138
151
  }
139
152
 
140
153
  # ── CLI tool — install plc_checkweigher command ───────────────────────────────
@@ -253,8 +266,8 @@ setup_smb() {
253
266
  prompt SMB_HOST "Host IP address" ""
254
267
  if [[ -z "${SMB_HOST}" ]]; then
255
268
  warn "SMB push disabled — no host entered."
256
- # Write a disabled smb_config.py
257
- cat > "${INSTALL_DIR}/smb_config.py" << 'EOF'
269
+ # Write a disabled smb_config.py to the pi-writable data/ directory.
270
+ cat > "${DATA_DIR}/smb_config.py" << 'EOF'
258
271
  # SMB push disabled during setup
259
272
  SMB_ENABLED = False
260
273
  SMB_HOST = ""
@@ -263,7 +276,7 @@ SMB_USERNAME = ""
263
276
  SMB_PASSWORD = ""
264
277
  SMB_SUBDIR = ""
265
278
  EOF
266
- chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/smb_config.py"
279
+ chown "${PI_USER}:${PI_USER}" "${DATA_DIR}/smb_config.py"
267
280
  return
268
281
  fi
269
282
 
@@ -275,8 +288,9 @@ EOF
275
288
  hr
276
289
  echo ""
277
290
 
278
- # Write smb_config.py (gitignored — credentials stay off GitHub)
279
- cat > "${INSTALL_DIR}/smb_config.py" << EOF
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
280
294
  # SMB configuration — written by setup.sh, NOT committed to git.
281
295
  SMB_ENABLED = True
282
296
  SMB_HOST = "${SMB_HOST}"
@@ -285,8 +299,8 @@ SMB_USERNAME = "${SMB_USERNAME}"
285
299
  SMB_PASSWORD = "${SMB_PASSWORD}"
286
300
  SMB_SUBDIR = "${SMB_SUBDIR}"
287
301
  EOF
288
- chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/smb_config.py"
289
- ok "SMB config saved → ${INSTALL_DIR}/smb_config.py"
302
+ chown "${PI_USER}:${PI_USER}" "${DATA_DIR}/smb_config.py"
303
+ ok "SMB config saved → ${DATA_DIR}/smb_config.py"
290
304
 
291
305
  # Test connectivity
292
306
  echo -n " Testing connection to ${SMB_HOST} ..."
@@ -479,13 +493,13 @@ setup_display() {
479
493
  # Start after hardware udev settles (HDMI/DSI detected) — not after network.
480
494
  After=systemd-udev-settle.service local-fs.target acpid.socket dbus.service
481
495
  Wants=systemd-udev-settle.service
496
+ # StartLimit* MUST be in [Unit] — ignored in [Service].
497
+ StartLimitBurst=10
498
+ StartLimitIntervalSec=60
482
499
 
483
500
  [Service]
484
- # Generous restart policy — display should always recover.
485
- StartLimitBurst=20
486
- StartLimitIntervalSec=120
487
501
  Restart=on-failure
488
- RestartSec=3
502
+ RestartSec=5
489
503
 
490
504
  # CPU cores 0-2 only — core 3 is reserved for SCHED_FIFO PLC process.
491
505
  CPUAffinity=0 1 2
@@ -495,7 +509,7 @@ Nice=-5
495
509
 
496
510
  LimitNOFILE=65536
497
511
  EOF
498
- ok "LightDM: CPUAffinity=0-2, Nice=-5, restarts up to 20×, network dep removed"
512
+ ok "LightDM: CPUAffinity=0-2, Nice=-5, StartLimitBurst=10 in [Unit]"
499
513
 
500
514
  # ── Fix utmpx — PAM needs /run/utmp to track sessions ───────────────────
501
515
  cat > /etc/tmpfiles.d/utmp-fix.conf << 'EOF'
@@ -504,13 +518,33 @@ EOF
504
518
  systemd-tmpfiles --create /etc/tmpfiles.d/utmp-fix.conf 2>/dev/null || true
505
519
  ok "/run/utmp fixed (utmpx PAM session tracking)"
506
520
 
507
- # gpu_mem is intentionally left at Pi OS firmware default.
508
- # display_auto_detect=1 (already in config.txt) handles VRAM and output
509
- # automatically whether the Pi is headless or a display is connected.
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
535
+ else
536
+ sed -i 's/^hdmi_force_hotplug=.*/hdmi_force_hotplug=1/' "${BOOT_FW}/config.txt"
537
+ fi
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)"
510
544
 
511
545
  systemctl daemon-reload
512
546
  systemctl enable lightdm.service 2>/dev/null || true
513
- ok "LightDM enabled — starts on every boot when display is connected"
547
+ ok "LightDM enabled — starts on every boot"
514
548
  }
515
549
 
516
550
  # ── 11b. VS Code server priority ──────────────────────────────────────────────
@@ -555,6 +589,58 @@ EOF
555
589
  ok "vscode-priority.service (cores 0-2, Nice=-5) — starts on every boot"
556
590
  }
557
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)"
642
+ }
643
+
558
644
  # ── 12. RT kernel — installed LAST so only one reboot is needed ───────────────
559
645
  install_rt_kernel() {
560
646
  step "PREEMPT_RT kernel (final step before reboot) ..."
@@ -614,10 +700,11 @@ do_reboot() {
614
700
  banner "Setup Complete"
615
701
  echo ""
616
702
  PI_IP="$(hostname -I | awk '{print $1}' 2>/dev/null || echo '<pi-ip>')"
617
- printf " ${G}%-32s${NC} %s\n" "Repo:" "${INSTALL_DIR}"
703
+ printf " ${G}%-32s${NC} %s\n" "Source (root-locked):" "${INSTALL_DIR}"
704
+ printf " ${G}%-32s${NC} %s\n" "Data (pi-writable):" "${DATA_DIR}"
618
705
  printf " ${G}%-32s${NC} %s\n" "Python venv:" "${VENV_DIR}"
619
706
  printf " ${G}%-32s${NC} %s\n" "Reports output:" "${REPORTS_DIR}"
620
- printf " ${G}%-32s${NC} %s\n" "SMB config:" "${INSTALL_DIR}/smb_config.py"
707
+ printf " ${G}%-32s${NC} %s\n" "SMB config:" "${DATA_DIR}/smb_config.py"
621
708
  printf " ${G}%-32s${NC} %s\n" "RT kernel:" "kernel8-rt.img (active after reboot)"
622
709
  printf " ${G}%-32s${NC} %s\n" "Stock kernel fallback:" "kernel8-stock.img"
623
710
  echo ""
@@ -662,6 +749,7 @@ main() {
662
749
  setup_boot_logo # 10 — Plymouth: logo + "SAI SAMARTH ENGG"
663
750
  setup_display # 11 — LightDM priority, CPU isolation, utmpx
664
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/
665
753
  install_rt_kernel # 12 — LAST, so only one reboot needed
666
754
  do_reboot # 12 — single reboot applies everything
667
755
  }