plc-checkweigher 1.1.0 → 1.3.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.
Files changed (3) hide show
  1. package/assets/logo.png +0 -0
  2. package/package.json +3 -2
  3. package/setup.sh +304 -183
Binary file
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "plc-checkweigher",
3
- "version": "1.1.0",
3
+ "version": "1.3.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
  "bin": {
6
6
  "plc-checkweigher": "bin/cli.js"
7
7
  },
8
8
  "files": [
9
9
  "bin/",
10
- "setup.sh"
10
+ "setup.sh",
11
+ "assets/"
11
12
  ],
12
13
  "keywords": [
13
14
  "plc",
package/setup.sh CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env bash
2
2
  # =============================================================================
3
- # PLC Check-Weigher — Full Stack Installer v1.1
3
+ # PLC Check-Weigher — Full Stack Installer v1.3
4
4
  # =============================================================================
5
- # Run on any fresh Raspberry Pi with ONE command:
5
+ # Run on any fresh Raspberry Pi:
6
6
  #
7
7
  # npx plc-checkweigher
8
8
  #
9
- # What happens (all in one go, single reboot at the end):
9
+ # Steps (everything first, ONE reboot at the very end):
10
10
  # 1. Pre-flight checks
11
- # 2. System packages (git, python3-venv, samba-client …)
12
- # 3. PREEMPT_RT kernel install + config (no reboot yet)
13
- # 4. Clone / update the plc-checkweigher repo
14
- # 5. Python venv + pip install (pinned versions)
15
- # 6. Create /home/pi/reports directory
16
- # 7. WiFi interactive scan pick from list enter password
17
- # 8. NetworkManager-wait-online (guarantees IP before services start)
18
- # 9. plc_watcher.service — SCHED_FIFO:50, IOClass=realtime, Core 3
19
- # 10. plc_web.service Nice=-10
20
- # 11. Enable + arm both services
21
- # 12. REBOOT (one-time, applies RT kernel + all config)
11
+ # 2. System packages
12
+ # 3. Clone / update repo
13
+ # 4. Python venv + pip install
14
+ # 5. Create /home/<user>/reports
15
+ # 6. WiFi — scan → pick from list → password
16
+ # 7. SMB enter host IP, share name, credentials smb_config.py
17
+ # 8. NetworkManager-wait-online
18
+ # 9. systemd services (plc_watcher + plc_web)
19
+ # 10. Boot logo Plymouth theme with logo.png + "SAI SAMARTH ENGG"
20
+ # 11. PREEMPT_RT kernel ← installed last so only one reboot is needed
21
+ # 12. REBOOT
22
22
  # =============================================================================
23
23
 
24
24
  set -euo pipefail
@@ -48,100 +48,63 @@ PY_PKGS=(
48
48
  )
49
49
 
50
50
  # ── Colours ───────────────────────────────────────────────────────────────────
51
- B='\033[1;34m'; G='\033[0;32m'; R='\033[1;31m'; Y='\033[1;33m'; C='\033[0;36m'; NC='\033[0m'
52
-
53
- banner() { echo -e "\n${B}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
54
- echo -e "${B} $*${NC}"
55
- echo -e "${B}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; }
56
- step() { echo -e "\n${Y}▶ $*${NC}"; }
57
- ok() { echo -e " ${G}✓${NC} $*"; }
58
- warn() { echo -e " ${Y}!${NC} $*"; }
59
- info() { echo -e " ${C}i${NC} $*"; }
60
- die() { echo -e "\n${R}FATAL:${NC} $*" >&2; exit 1; }
61
- hr() { echo -e " ${B}$(printf '─%.0s' {1..48})${NC}"; }
51
+ B='\033[1;34m'; G='\033[0;32m'; R='\033[1;31m'; Y='\033[1;33m'
52
+ C='\033[0;36m'; D='\033[2m'; NC='\033[0m'
53
+
54
+ banner() { echo -e "\n${B}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
55
+ echo -e "${B} $*${NC}"
56
+ echo -e "${B}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; }
57
+ step() { echo -e "\n${Y}▶ $*${NC}"; }
58
+ ok() { echo -e " ${G}✓${NC} $*"; }
59
+ warn() { echo -e " ${Y}!${NC} $*"; }
60
+ info() { echo -e " ${C}i${NC} $*"; }
61
+ hr() { echo -e " ${D}$(printf '─%.0s' {1..50})${NC}"; }
62
+ die() { echo -e "\n${R}FATAL:${NC} $*" >&2; exit 1; }
63
+
64
+ prompt() {
65
+ # prompt <varname> <display-label> [default]
66
+ local var="$1" label="$2" default="${3:-}"
67
+ local hint=""
68
+ [[ -n "$default" ]] && hint=" ${D}[${default}]${NC}"
69
+ printf " %-28s%b: " "${label}" "${hint}"
70
+ read -r value </dev/tty
71
+ [[ -z "$value" && -n "$default" ]] && value="$default"
72
+ printf -v "$var" '%s' "$value"
73
+ }
74
+
75
+ prompt_secret() {
76
+ local var="$1" label="$2"
77
+ printf " %-28s: " "${label}"
78
+ read -r -s value </dev/tty
79
+ echo ""
80
+ printf -v "$var" '%s' "$value"
81
+ }
62
82
 
63
83
  # ── 0. Pre-flight ─────────────────────────────────────────────────────────────
64
84
  preflight() {
65
- banner "PLC Check-Weigher Installer v1.1"
66
-
67
- [[ "${EUID}" -eq 0 ]] || die "Run via: npx plc-checkweigher (asks for sudo password)"
68
- [[ "$(uname -m)" == "aarch64" ]] || die "Requires 64-bit Raspberry Pi (aarch64). Got: $(uname -m)"
69
- [[ -d "${HOME_DIR}" ]] || die "Home dir ${HOME_DIR} not found. Set PI_USER= to override."
70
- command -v nmcli &>/dev/null || die "NetworkManager not found — install Raspberry Pi OS first."
71
-
85
+ banner "PLC Check-Weigher Installer v1.2"
86
+ [[ "${EUID}" -eq 0 ]] || die "Run via npx plc-checkweigher (asks for sudo password)"
87
+ [[ "$(uname -m)" == "aarch64" ]] || die "Requires 64-bit Raspberry Pi (aarch64). Got: $(uname -m)"
88
+ [[ -d "${HOME_DIR}" ]] || die "Home ${HOME_DIR} not found. Set PI_USER= to override."
89
+ command -v nmcli &>/dev/null || die "NetworkManager not found install Raspberry Pi OS first."
72
90
  info "Host : $(hostname)"
73
91
  info "Kernel : $(uname -r)"
74
92
  info "User : ${PI_USER}"
75
- info "Repo : ${REPO_URL}"
76
93
  }
77
94
 
78
95
  # ── 1. System packages ────────────────────────────────────────────────────────
79
96
  install_system_packages() {
80
- step "Installing system packages ..."
97
+ step "System packages ..."
81
98
  DEBIAN_FRONTEND=noninteractive apt-get update -qq
82
99
  DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
83
100
  git python3-venv python3-pip python3-dev \
84
101
  samba-client cifs-utils network-manager curl build-essential
85
- ok "git, python3-venv, python3-dev, samba-client, cifs-utils"
86
- }
87
-
88
- # ── 2. RT kernel ──────────────────────────────────────────────────────────────
89
- install_rt_kernel() {
90
- step "Setting up PREEMPT_RT kernel ..."
91
-
92
- if grep -q "PREEMPT_RT" /proc/version 2>/dev/null; then
93
- ok "Already running PREEMPT_RT kernel — skipping install"
94
- return
95
- fi
96
-
97
- # Backup
98
- if [[ ! -f "${BOOT_FW}/kernel8-stock.img" ]]; then
99
- cp "${BOOT_FW}/kernel8.img" "${BOOT_FW}/kernel8-stock.img"
100
- ok "Stock kernel backed up → kernel8-stock.img"
101
- fi
102
-
103
- CHKSUM_BEFORE="$(md5sum "${BOOT_FW}/kernel8.img" | cut -d' ' -f1)"
104
- DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${RT_PKG}" "${RT_HDR}"
105
- CHKSUM_AFTER="$(md5sum "${BOOT_FW}/kernel8.img" | cut -d' ' -f1)"
106
-
107
- if [[ "${CHKSUM_BEFORE}" != "${CHKSUM_AFTER}" ]]; then
108
- cp "${BOOT_FW}/kernel8.img" "${BOOT_FW}/kernel8-rt.img"
109
- cp "${BOOT_FW}/kernel8-stock.img" "${BOOT_FW}/kernel8.img"
110
- ok "RT kernel → kernel8-rt.img | stock restored as fallback"
111
- else
112
- RT_VMLINUZ="$(ls /boot/vmlinuz-*rt-arm64 2>/dev/null | sort -V | tail -1)"
113
- [[ -n "${RT_VMLINUZ}" ]] || die "RT vmlinuz not found in /boot/"
114
- if file "${RT_VMLINUZ}" | grep -q gzip; then
115
- zcat "${RT_VMLINUZ}" > "${BOOT_FW}/kernel8-rt.img"
116
- else
117
- cp "${RT_VMLINUZ}" "${BOOT_FW}/kernel8-rt.img"
118
- fi
119
- ok "RT kernel manually copied → kernel8-rt.img"
120
- fi
121
-
122
- # Copy RT initramfs if present
123
- RT_INITRD="$(ls /boot/initrd.img-*rt-arm64 2>/dev/null | sort -V | tail -1 || true)"
124
- [[ -n "${RT_INITRD}" ]] && cp "${RT_INITRD}" "${BOOT_FW}/initramfs8-rt" \
125
- && ok "RT initramfs → initramfs8-rt"
126
-
127
- # Activate in config.txt (idempotent — removes any previous block first)
128
- sed -i '/### PLC-RT-BLOCK-START ###/,/### PLC-RT-BLOCK-END ###/d' \
129
- "${BOOT_FW}/config.txt"
130
- cat >> "${BOOT_FW}/config.txt" << 'EOF'
131
-
132
- ### PLC-RT-BLOCK-START ###
133
- # PREEMPT_RT kernel — installed by plc-checkweigher setup.sh
134
- # To revert: comment the two lines below and reboot.
135
- kernel=kernel8-rt.img
136
- initramfs initramfs8-rt followkernel
137
- ### PLC-RT-BLOCK-END ###
138
- EOF
139
- ok "config.txt updated — RT kernel activates after reboot"
102
+ ok "git python3-venv samba-client cifs-utils build-essential"
140
103
  }
141
104
 
142
- # ── 3. Clone / update repo ────────────────────────────────────────────────────
105
+ # ── 2. Clone / update repo ────────────────────────────────────────────────────
143
106
  setup_repo() {
144
- step "Setting up repository ..."
107
+ step "Repository ..."
145
108
  if [[ -d "${INSTALL_DIR}/.git" ]]; then
146
109
  sudo -u "${PI_USER}" git -C "${INSTALL_DIR}" pull --ff-only origin "${REPO_BRANCH}" \
147
110
  && ok "Repo updated → ${INSTALL_DIR}" \
@@ -153,41 +116,38 @@ setup_repo() {
153
116
  fi
154
117
  }
155
118
 
156
- # ── 4. Python venv ────────────────────────────────────────────────────────────
119
+ # ── 3. Python venv ────────────────────────────────────────────────────────────
157
120
  setup_venv() {
158
- step "Setting up Python environment ..."
121
+ step "Python environment ..."
159
122
  [[ -d "${VENV_DIR}" ]] \
160
123
  && ok "venv exists — skipping creation" \
161
124
  || sudo -u "${PI_USER}" python3 -m venv "${VENV_DIR}"
162
125
  sudo -u "${PI_USER}" "${VENV_DIR}/bin/pip" install -q --upgrade pip
163
126
  sudo -u "${PI_USER}" "${VENV_DIR}/bin/pip" install -q "${PY_PKGS[@]}"
164
- ok "Packages installed in ${VENV_DIR}"
127
+ ok "Packages installed${VENV_DIR}"
165
128
  }
166
129
 
167
- # ── 5. Directories ────────────────────────────────────────────────────────────
130
+ # ── 4. Directories ────────────────────────────────────────────────────────────
168
131
  setup_dirs() {
169
- step "Creating runtime directories ..."
132
+ step "Runtime directories ..."
170
133
  mkdir -p "${REPORTS_DIR}"
171
134
  chown "${PI_USER}:${PI_USER}" "${REPORTS_DIR}"
172
135
  ok "${REPORTS_DIR}"
173
136
  }
174
137
 
175
- # ── 6. WiFi — interactive scan + pick ────────────────────────────────────────
138
+ # ── 5. WiFi — scan pick → password ─────────────────────────────────────────
176
139
  setup_wifi() {
177
140
  step "WiFi Setup"
178
141
 
179
- # Check if wlan0 exists
180
142
  if ! ip link show wlan0 &>/dev/null; then
181
- warn "No wlan0 interface found — skipping WiFi setup."
143
+ warn "No wlan0 found — skipping WiFi setup."
182
144
  return
183
145
  fi
184
146
 
185
- echo ""
186
- echo -e " ${C}Scanning for nearby networks ...${NC}"
147
+ echo -e "\n ${C}Scanning for networks ...${NC}"
187
148
  nmcli dev wifi rescan ifname wlan0 2>/dev/null || true
188
149
  sleep 3
189
150
 
190
- # Build deduplicated, signal-sorted list [SSID, SIGNAL, SECURITY]
191
151
  mapfile -t RAW < <(
192
152
  nmcli -t -f SSID,SIGNAL,SECURITY dev wifi list ifname wlan0 2>/dev/null \
193
153
  | grep -v '^:' \
@@ -197,65 +157,47 @@ setup_wifi() {
197
157
  )
198
158
 
199
159
  if [[ ${#RAW[@]} -eq 0 ]]; then
200
- warn "No networks found. Skipping WiFi setup."
160
+ warn "No networks found skipping WiFi setup."
201
161
  return
202
162
  fi
203
163
 
204
- # ── Print menu ────────────────────────────────────────────────
205
164
  echo ""
206
165
  hr
207
166
  printf " ${B}%-4s %-28s %-10s %s${NC}\n" "#" "SSID" "Signal" "Security"
208
167
  hr
209
-
210
168
  declare -a SSIDS SIGNALS SECURITIES
211
169
  for i in "${!RAW[@]}"; do
212
170
  IFS=':' read -r SSID SIGNAL SECURITY <<< "${RAW[$i]}"
213
- SSIDS[$i]="${SSID}"
214
- SIGNALS[$i]="${SIGNAL}"
215
- SECURITIES[$i]="${SECURITY}"
216
-
217
- # Visual signal bar
171
+ SSIDS[$i]="${SSID}"; SIGNALS[$i]="${SIGNAL}"; SECURITIES[$i]="${SECURITY}"
218
172
  SIG="${SIGNAL:-0}"
219
173
  if [[ $SIG -ge 80 ]]; then BAR="${G}▂▄▆█${NC}"
220
- elif [[ $SIG -ge 60 ]]; then BAR="${G}▂▄▆${NC} "
221
- elif [[ $SIG -ge 40 ]]; then BAR="${Y}▂▄${NC} "
222
- else BAR="${R}▂${NC} "; fi
223
-
224
- SEC="${SECURITY:---}"
225
- printf " %-4s %-28s %b %-4s%% %s\n" \
226
- "$((i+1)))" "${SSID}" "${BAR}" "${SIG}" "${SEC}"
174
+ elif [[ $SIG -ge 60 ]]; then BAR="${G}▂▄▆ ${NC}"
175
+ elif [[ $SIG -ge 40 ]]; then BAR="${Y}▂▄ ${NC}"
176
+ else BAR="${R}▂ ${NC}"; fi
177
+ printf " %-4s %-28s %b %3s%% %s\n" \
178
+ "$((i+1)))" "${SSID}" "${BAR}" "${SIG}" "${SECURITY:---}"
227
179
  done
228
-
229
180
  hr
230
181
  printf " %-4s %s\n" "0)" "Skip WiFi setup"
231
182
  echo ""
232
183
 
233
- # ── Read choice ───────────────────────────────────────────────
234
184
  while true; do
235
185
  read -r -p " Choose network [1-${#RAW[@]}] or 0 to skip: " CHOICE </dev/tty
236
186
  [[ "$CHOICE" =~ ^[0-9]+$ ]] && \
237
- [[ "$CHOICE" -ge 0 ]] && [[ "$CHOICE" -le "${#RAW[@]}" ]] && break
238
- echo -e " ${R}Invalid choice — enter a number between 0 and ${#RAW[@]}${NC}"
187
+ [[ "$CHOICE" -ge 0 && "$CHOICE" -le "${#RAW[@]}" ]] && break
188
+ echo -e " ${R}Enter a number between 0 and ${#RAW[@]}${NC}"
239
189
  done
240
190
 
241
- if [[ "$CHOICE" -eq 0 ]]; then
242
- warn "WiFi setup skipped."
243
- return
244
- fi
191
+ [[ "$CHOICE" -eq 0 ]] && { warn "WiFi setup skipped."; return; }
245
192
 
246
193
  IDX=$((CHOICE - 1))
247
194
  SEL_SSID="${SSIDS[$IDX]}"
248
195
  SEL_SEC="${SECURITIES[$IDX]}"
249
196
 
250
- echo ""
251
- ok "Selected: ${SEL_SSID}"
252
-
253
- # ── Password prompt (skip for open networks) ──────────────────
254
197
  WIFI_PASS=""
255
198
  if [[ "${SEL_SEC}" != "--" && -n "${SEL_SEC}" ]]; then
256
199
  while true; do
257
- read -r -s -p " Enter WiFi password: " WIFI_PASS </dev/tty
258
- echo ""
200
+ prompt_secret WIFI_PASS "WiFi password"
259
201
  [[ -n "${WIFI_PASS}" ]] && break
260
202
  echo -e " ${R}Password cannot be empty for a secured network.${NC}"
261
203
  done
@@ -263,42 +205,92 @@ setup_wifi() {
263
205
  info "Open network — no password needed."
264
206
  fi
265
207
 
266
- # ── Create / update the NM connection profile ─────────────────
267
- # Remove any existing profile with this SSID to avoid conflicts
268
208
  nmcli connection delete "${SEL_SSID}" 2>/dev/null || true
269
-
270
209
  if [[ -n "${WIFI_PASS}" ]]; then
271
- nmcli connection add \
272
- type wifi ifname wlan0 \
273
- con-name "${SEL_SSID}" \
274
- ssid "${SEL_SSID}" \
275
- wifi-sec.key-mgmt wpa-psk \
276
- wifi-sec.psk "${WIFI_PASS}" \
277
- connection.autoconnect yes \
278
- connection.autoconnect-priority 200 2>/dev/null
210
+ nmcli connection add type wifi ifname wlan0 con-name "${SEL_SSID}" \
211
+ ssid "${SEL_SSID}" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "${WIFI_PASS}" \
212
+ connection.autoconnect yes connection.autoconnect-priority 200 2>/dev/null
279
213
  else
280
- nmcli connection add \
281
- type wifi ifname wlan0 \
282
- con-name "${SEL_SSID}" \
283
- ssid "${SEL_SSID}" \
284
- connection.autoconnect yes \
214
+ nmcli connection add type wifi ifname wlan0 con-name "${SEL_SSID}" \
215
+ ssid "${SEL_SSID}" connection.autoconnect yes \
285
216
  connection.autoconnect-priority 200 2>/dev/null
286
217
  fi
287
218
 
288
- # Connect now so the rest of the setup (git pull, pip) works
289
219
  echo -n " Connecting ..."
290
220
  if nmcli connection up "${SEL_SSID}" 2>/dev/null; then
291
- echo ""
292
- ok "Connected to '${SEL_SSID}'"
221
+ echo ""; ok "Connected to '${SEL_SSID}'"
293
222
  else
223
+ echo ""; warn "Will connect automatically after reboot."
224
+ fi
225
+ }
226
+
227
+ # ── 6. SMB file sharing — interactive ────────────────────────────────────────
228
+ setup_smb() {
229
+ step "SMB File Sharing Setup"
230
+ echo ""
231
+ echo -e " ${C}PDF reports will be pushed to a shared folder on another PC.${NC}"
232
+ echo -e " ${D}Leave blank to disable SMB push.${NC}"
233
+ echo ""
234
+ hr
235
+
236
+ prompt SMB_HOST "Host IP address" ""
237
+ if [[ -z "${SMB_HOST}" ]]; then
238
+ warn "SMB push disabled — no host entered."
239
+ # Write a disabled smb_config.py
240
+ cat > "${INSTALL_DIR}/smb_config.py" << 'EOF'
241
+ # SMB push disabled during setup
242
+ SMB_ENABLED = False
243
+ SMB_HOST = ""
244
+ SMB_SHARE = ""
245
+ SMB_USERNAME = ""
246
+ SMB_PASSWORD = ""
247
+ SMB_SUBDIR = ""
248
+ EOF
249
+ chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/smb_config.py"
250
+ return
251
+ fi
252
+
253
+ prompt SMB_SHARE "Share name (folder)" "Reports"
254
+ prompt SMB_USERNAME "Username" ""
255
+ prompt_secret SMB_PASSWORD "Password"
256
+ prompt SMB_SUBDIR "Subfolder (optional)" ""
257
+
258
+ hr
259
+ echo ""
260
+
261
+ # Write smb_config.py (gitignored — credentials stay off GitHub)
262
+ cat > "${INSTALL_DIR}/smb_config.py" << EOF
263
+ # SMB configuration — written by setup.sh, NOT committed to git.
264
+ SMB_ENABLED = True
265
+ SMB_HOST = "${SMB_HOST}"
266
+ SMB_SHARE = "${SMB_SHARE}"
267
+ SMB_USERNAME = "${SMB_USERNAME}"
268
+ SMB_PASSWORD = "${SMB_PASSWORD}"
269
+ SMB_SUBDIR = "${SMB_SUBDIR}"
270
+ EOF
271
+ chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/smb_config.py"
272
+ ok "SMB config saved → ${INSTALL_DIR}/smb_config.py"
273
+
274
+ # Test connectivity
275
+ echo -n " Testing connection to ${SMB_HOST} ..."
276
+ if ping -c 2 -W 2 "${SMB_HOST}" &>/dev/null; then
294
277
  echo ""
295
- warn "Could not connect now — will auto-connect on reboot."
278
+ ok "Host ${SMB_HOST} reachable"
279
+ echo -n " Authenticating with share //${SMB_HOST}/${SMB_SHARE} ..."
280
+ if smbclient "//${SMB_HOST}/${SMB_SHARE}" \
281
+ -U "${SMB_USERNAME}%${SMB_PASSWORD}" -c "ls" &>/dev/null 2>&1; then
282
+ echo ""; ok "SMB share authenticated — PDF push is ready"
283
+ else
284
+ echo ""; warn "Auth failed — verify credentials and share name after reboot."
285
+ fi
286
+ else
287
+ echo ""; warn "${SMB_HOST} not reachable now — will retry at runtime."
296
288
  fi
297
289
  }
298
290
 
299
291
  # ── 7. Network-online guarantee ───────────────────────────────────────────────
300
292
  setup_network_online() {
301
- step "Enabling NetworkManager-wait-online (60 s timeout) ..."
293
+ step "NetworkManager-wait-online ..."
302
294
  mkdir -p /etc/systemd/system/NetworkManager-wait-online.service.d/
303
295
  cat > /etc/systemd/system/NetworkManager-wait-online.service.d/timeout.conf << 'EOF'
304
296
  [Service]
@@ -306,14 +298,13 @@ ExecStart=
306
298
  ExecStart=/usr/lib/NetworkManager/nm-online -s -q --timeout=60
307
299
  EOF
308
300
  systemctl enable NetworkManager-wait-online.service 2>/dev/null || true
309
- ok "Services will wait up to 60 s for a network connection after boot"
301
+ ok "Services will wait up to 60 s for network on each boot"
310
302
  }
311
303
 
312
- # ── 8 & 9. systemd services ───────────────────────────────────────────────────
304
+ # ── 8. systemd services ───────────────────────────────────────────────────────
313
305
  install_services() {
314
- step "Installing systemd services ..."
306
+ step "systemd services ..."
315
307
 
316
- # plc_watcher — SCHED_FIFO:50, IOClass=realtime, pinned to CPU core 3
317
308
  cat > /etc/systemd/system/plc_watcher.service << EOF
318
309
  [Unit]
319
310
  Description=PLC Check-Weigher Start Watcher
@@ -342,7 +333,6 @@ WantedBy=multi-user.target
342
333
  EOF
343
334
  ok "plc_watcher.service (SCHED_FIFO:50 · IOClass=realtime · CPU core 3)"
344
335
 
345
- # plc_web — elevated but not real-time
346
336
  cat > /etc/systemd/system/plc_web.service << EOF
347
337
  [Unit]
348
338
  Description=PLC Check-Weigher Report Viewer
@@ -369,38 +359,167 @@ EOF
369
359
 
370
360
  systemctl daemon-reload
371
361
  systemctl enable plc_watcher.service plc_web.service
372
- ok "Both services enabled — will start automatically after reboot"
362
+ ok "Both services enabled — start automatically after reboot"
373
363
 
374
- # Mirror updated service file back into the repo
375
364
  cp /etc/systemd/system/plc_watcher.service "${INSTALL_DIR}/plc_watcher.service"
376
365
  chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/plc_watcher.service"
377
366
  }
378
367
 
368
+ # ── 10. Boot splash — Plymouth theme with logo + company name ────────────────
369
+ setup_boot_logo() {
370
+ step "Boot splash screen ..."
371
+
372
+ # Install Plymouth + font support (safe to run even if already installed)
373
+ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
374
+ plymouth plymouth-themes fonts-freefont-ttf
375
+
376
+ THEME_DIR="/usr/share/plymouth/themes/saismruth"
377
+ mkdir -p "${THEME_DIR}"
378
+
379
+ # ── Logo: resize assets/logo.png to 256×256 and copy into theme ──────────
380
+ LOGO_SRC="${INSTALL_DIR}/assets/logo.png"
381
+ if [[ -f "${LOGO_SRC}" ]]; then
382
+ "${VENV_DIR}/bin/python3" - << PYEOF
383
+ from PIL import Image
384
+ img = Image.open("${LOGO_SRC}").convert("RGBA")
385
+ img.thumbnail((256, 256), Image.LANCZOS)
386
+ img.save("${THEME_DIR}/logo.png", "PNG")
387
+ PYEOF
388
+ ok "Logo installed (256×256) → ${THEME_DIR}/logo.png"
389
+ else
390
+ warn "assets/logo.png not found — splash will show text only"
391
+ fi
392
+
393
+ # ── Theme config file ─────────────────────────────────────────────────────
394
+ cat > "${THEME_DIR}/saismruth.plymouth" << 'EOF'
395
+ [Plymouth Theme]
396
+ Name=SAI SAMARTH ENGG
397
+ Description=PLC Check-Weigher Boot Screen — SAI SAMARTH ENGG
398
+ ModuleName=script
399
+
400
+ [script]
401
+ ImageDir=/usr/share/plymouth/themes/saismruth
402
+ ScriptFile=/usr/share/plymouth/themes/saismruth/saismruth.script
403
+ EOF
404
+
405
+ # ── Plymouth script: logo centred, text below ─────────────────────────────
406
+ cat > "${THEME_DIR}/saismruth.script" << 'EOF'
407
+ # ── SAI SAMARTH ENGG — Boot Splash ────────────────────────────────────────────
408
+ Window.SetBackgroundTopColor(0.0, 0.0, 0.0);
409
+ Window.SetBackgroundBottomColor(0.0, 0.0, 0.0);
410
+
411
+ screen_w = Window.GetWidth();
412
+ screen_h = Window.GetHeight();
413
+
414
+ # ── Logo (centred, slightly above middle to leave room for text) ──────────────
415
+ logo_img = Image("logo.png");
416
+ logo_w = logo_img.GetWidth();
417
+ logo_h = logo_img.GetHeight();
418
+ logo_x = (screen_w - logo_w) / 2;
419
+ logo_y = (screen_h - logo_h) / 2 - 40;
420
+
421
+ logo_sprite = Sprite(logo_img);
422
+ logo_sprite.SetPosition(logo_x, logo_y, 0);
423
+
424
+ # ── Company name centred below logo ───────────────────────────────────────────
425
+ text_img = Image.Text("SAI SAMARTH ENGG", 1.0, 1.0, 1.0, 1.0, "Sans Bold 20");
426
+ text_w = text_img.GetWidth();
427
+ text_x = (screen_w - text_w) / 2;
428
+ text_y = logo_y + logo_h + 22;
429
+
430
+ text_sprite = Sprite(text_img);
431
+ text_sprite.SetPosition(text_x, text_y, 1);
432
+ EOF
433
+
434
+ # ── Activate theme ────────────────────────────────────────────────────────
435
+ plymouth-set-default-theme saismruth
436
+ ok "Plymouth theme set → saismruth"
437
+
438
+ # Rebuild current initramfs so Plymouth is included.
439
+ # The RT kernel's post-install will create its own initramfs with Plymouth
440
+ # already installed, so initramfs8-rt will also carry the theme.
441
+ echo -n " Rebuilding initramfs (may take ~30 s) ..."
442
+ update-initramfs -u > /tmp/initramfs.log 2>&1 \
443
+ && echo "" && ok "initramfs rebuilt" \
444
+ || { echo ""; warn "initramfs warnings — see /tmp/initramfs.log"; }
445
+ }
446
+
447
+ # ── 11. RT kernel — installed LAST so only one reboot is needed ───────────────
448
+ install_rt_kernel() {
449
+ step "PREEMPT_RT kernel (final step before reboot) ..."
450
+
451
+ if grep -q "PREEMPT_RT" /proc/version 2>/dev/null; then
452
+ ok "Already running PREEMPT_RT — skipping kernel install"
453
+ return
454
+ fi
455
+
456
+ # Backup stock kernel
457
+ if [[ ! -f "${BOOT_FW}/kernel8-stock.img" ]]; then
458
+ cp "${BOOT_FW}/kernel8.img" "${BOOT_FW}/kernel8-stock.img"
459
+ ok "Stock kernel backed up → kernel8-stock.img"
460
+ fi
461
+
462
+ CHKSUM_BEFORE="$(md5sum "${BOOT_FW}/kernel8.img" | cut -d' ' -f1)"
463
+ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${RT_PKG}" "${RT_HDR}"
464
+ CHKSUM_AFTER="$(md5sum "${BOOT_FW}/kernel8.img" | cut -d' ' -f1)"
465
+
466
+ if [[ "${CHKSUM_BEFORE}" != "${CHKSUM_AFTER}" ]]; then
467
+ cp "${BOOT_FW}/kernel8.img" "${BOOT_FW}/kernel8-rt.img"
468
+ cp "${BOOT_FW}/kernel8-stock.img" "${BOOT_FW}/kernel8.img"
469
+ ok "RT kernel → kernel8-rt.img | stock restored as boot default"
470
+ else
471
+ RT_VMLINUZ="$(ls /boot/vmlinuz-*rt-arm64 2>/dev/null | sort -V | tail -1)"
472
+ [[ -n "${RT_VMLINUZ}" ]] || die "RT vmlinuz not found in /boot/"
473
+ if file "${RT_VMLINUZ}" | grep -q gzip; then
474
+ zcat "${RT_VMLINUZ}" > "${BOOT_FW}/kernel8-rt.img"
475
+ else
476
+ cp "${RT_VMLINUZ}" "${BOOT_FW}/kernel8-rt.img"
477
+ fi
478
+ ok "RT kernel manually copied → kernel8-rt.img"
479
+ fi
480
+
481
+ RT_INITRD="$(ls /boot/initrd.img-*rt-arm64 2>/dev/null | sort -V | tail -1 || true)"
482
+ [[ -n "${RT_INITRD}" ]] && cp "${RT_INITRD}" "${BOOT_FW}/initramfs8-rt" \
483
+ && ok "RT initramfs → initramfs8-rt"
484
+
485
+ # Activate in config.txt (idempotent)
486
+ sed -i '/### PLC-RT-BLOCK-START ###/,/### PLC-RT-BLOCK-END ###/d' \
487
+ "${BOOT_FW}/config.txt"
488
+ cat >> "${BOOT_FW}/config.txt" << 'EOF'
489
+
490
+ ### PLC-RT-BLOCK-START ###
491
+ # PREEMPT_RT kernel — installed by plc-checkweigher setup.sh
492
+ # To revert to stock: comment the two lines below and reboot.
493
+ kernel=kernel8-rt.img
494
+ initramfs initramfs8-rt followkernel
495
+ ### PLC-RT-BLOCK-END ###
496
+ EOF
497
+ ok "config.txt updated — RT kernel activates on reboot"
498
+ }
499
+
379
500
  # ── Summary + countdown reboot ────────────────────────────────────────────────
380
501
  do_reboot() {
381
502
  echo ""
382
- banner "All Done — Summary"
503
+ banner "Setup Complete"
383
504
  echo ""
384
-
385
- printf " %-30s %s\n" "RT kernel ready:" "kernel8-rt.img (activates on reboot)"
386
- printf " %-30s %s\n" "Stock kernel fallback:" "kernel8-stock.img"
387
- printf " %-30s %s\n" "Repo:" "${INSTALL_DIR}"
388
- printf " %-30s %s\n" "Python venv:" "${VENV_DIR}"
389
- printf " %-30s %s\n" "Reports output:" "${REPORTS_DIR}"
390
- printf " %-30s %s\n" "Web dashboard (after reboot):" \
505
+ printf " ${G}%-32s${NC} %s\n" "Repo:" "${INSTALL_DIR}"
506
+ printf " ${G}%-32s${NC} %s\n" "Python venv:" "${VENV_DIR}"
507
+ printf " ${G}%-32s${NC} %s\n" "Reports output:" "${REPORTS_DIR}"
508
+ printf " ${G}%-32s${NC} %s\n" "RT kernel:" "kernel8-rt.img (active after reboot)"
509
+ printf " ${G}%-32s${NC} %s\n" "Stock kernel fallback:" "kernel8-stock.img"
510
+ printf " ${G}%-32s${NC} %s\n" "Web dashboard (after reboot):" \
391
511
  "http://$(hostname -I | awk '{print $1}' 2>/dev/null || echo '<pi-ip>'):8080"
392
-
393
512
  echo ""
394
- echo -e " ${Y}After reboot, check services with:${NC}"
395
- echo " journalctl -u plc_watcher -f"
396
- echo " sudo chrt -p \$(systemctl show -p MainPID --value plc_watcher)"
513
+ echo -e " ${Y}After reboot:${NC}"
514
+ echo " journalctl -u plc_watcher -f # live logs"
515
+ echo " sudo chrt -p \$(systemctl show -p MainPID --value plc_watcher) # verify RT"
397
516
  echo ""
398
517
 
399
518
  echo -e "${G}"
400
519
  echo " ╔══════════════════════════════════════════════════════╗"
401
- echo " ║ Rebooting in 10 seconds to apply all changes. ║"
402
- echo " ║ Press Ctrl+C to cancel — reboot manually later: ║"
403
- echo " ║ sudo reboot ║"
520
+ echo " ║ All done. Rebooting in 10 seconds to apply all ║"
521
+ echo " ║ changes including the RT kernel. ║"
522
+ echo " ║ Press Ctrl+C to cancel — then: sudo reboot ║"
404
523
  echo " ╚══════════════════════════════════════════════════════╝"
405
524
  echo -e "${NC}"
406
525
 
@@ -415,15 +534,17 @@ do_reboot() {
415
534
  # ── Main ──────────────────────────────────────────────────────────────────────
416
535
  main() {
417
536
  preflight
418
- install_system_packages
419
- install_rt_kernel
420
- setup_repo
421
- setup_venv
422
- setup_dirs
423
- setup_wifi
424
- setup_network_online
425
- install_services
426
- do_reboot
537
+ install_system_packages # 1
538
+ setup_repo # 2
539
+ setup_venv # 3
540
+ setup_dirs # 4
541
+ setup_wifi # 5 — interactive WiFi picker
542
+ setup_smb # 6 — interactive SMB config → smb_config.py
543
+ setup_network_online # 7
544
+ install_services # 8
545
+ setup_boot_logo # 9 — Plymouth: logo + "SAI SAMARTH ENGG"
546
+ install_rt_kernel # 10 — LAST, so only one reboot needed
547
+ do_reboot # 11 — single reboot applies everything
427
548
  }
428
549
 
429
550
  main "$@"