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.
- package/assets/logo.png +0 -0
- package/package.json +3 -2
- package/setup.sh +304 -183
package/assets/logo.png
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plc-checkweigher",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
3
|
+
# PLC Check-Weigher — Full Stack Installer v1.3
|
|
4
4
|
# =============================================================================
|
|
5
|
-
# Run on any fresh Raspberry Pi
|
|
5
|
+
# Run on any fresh Raspberry Pi:
|
|
6
6
|
#
|
|
7
7
|
# npx plc-checkweigher
|
|
8
8
|
#
|
|
9
|
-
#
|
|
9
|
+
# Steps (everything first, ONE reboot at the very end):
|
|
10
10
|
# 1. Pre-flight checks
|
|
11
|
-
# 2. System packages
|
|
12
|
-
# 3.
|
|
13
|
-
# 4.
|
|
14
|
-
# 5.
|
|
15
|
-
# 6.
|
|
16
|
-
# 7.
|
|
17
|
-
# 8. NetworkManager-wait-online
|
|
18
|
-
# 9.
|
|
19
|
-
# 10.
|
|
20
|
-
# 11.
|
|
21
|
-
# 12. REBOOT
|
|
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'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
hr()
|
|
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.
|
|
66
|
-
|
|
67
|
-
[[ "$
|
|
68
|
-
[[
|
|
69
|
-
|
|
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 "
|
|
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
|
|
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
|
-
# ──
|
|
105
|
+
# ── 2. Clone / update repo ────────────────────────────────────────────────────
|
|
143
106
|
setup_repo() {
|
|
144
|
-
step "
|
|
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
|
-
# ──
|
|
119
|
+
# ── 3. Python venv ────────────────────────────────────────────────────────────
|
|
157
120
|
setup_venv() {
|
|
158
|
-
step "
|
|
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
|
|
127
|
+
ok "Packages installed → ${VENV_DIR}"
|
|
165
128
|
}
|
|
166
129
|
|
|
167
|
-
# ──
|
|
130
|
+
# ── 4. Directories ────────────────────────────────────────────────────────────
|
|
168
131
|
setup_dirs() {
|
|
169
|
-
step "
|
|
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
|
-
# ──
|
|
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
|
|
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
|
|
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}
|
|
221
|
-
elif [[ $SIG -ge 40 ]]; then BAR="${Y}
|
|
222
|
-
else BAR="${R}
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
238
|
-
echo -e " ${R}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
|
301
|
+
ok "Services will wait up to 60 s for network on each boot"
|
|
310
302
|
}
|
|
311
303
|
|
|
312
|
-
# ── 8
|
|
304
|
+
# ── 8. systemd services ───────────────────────────────────────────────────────
|
|
313
305
|
install_services() {
|
|
314
|
-
step "
|
|
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 —
|
|
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 "
|
|
503
|
+
banner "Setup Complete"
|
|
383
504
|
echo ""
|
|
384
|
-
|
|
385
|
-
printf " %-
|
|
386
|
-
printf " %-
|
|
387
|
-
printf " %-
|
|
388
|
-
printf " %-
|
|
389
|
-
printf " %-
|
|
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
|
|
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
|
|
402
|
-
echo " ║
|
|
403
|
-
echo " ║
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
setup_network_online
|
|
425
|
-
install_services
|
|
426
|
-
|
|
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 "$@"
|