plc-checkweigher 1.1.0 → 1.2.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/package.json +1 -1
- package/setup.sh +223 -183
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plc-checkweigher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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"
|
package/setup.sh
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# =============================================================================
|
|
3
|
-
# PLC Check-Weigher — Full Stack Installer v1.
|
|
3
|
+
# PLC Check-Weigher — Full Stack Installer v1.2
|
|
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 (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. PREEMPT_RT kernel ← installed last so only one reboot is needed
|
|
20
|
+
# 11. REBOOT
|
|
22
21
|
# =============================================================================
|
|
23
22
|
|
|
24
23
|
set -euo pipefail
|
|
@@ -48,100 +47,63 @@ PY_PKGS=(
|
|
|
48
47
|
)
|
|
49
48
|
|
|
50
49
|
# ── 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()
|
|
50
|
+
B='\033[1;34m'; G='\033[0;32m'; R='\033[1;31m'; Y='\033[1;33m'
|
|
51
|
+
C='\033[0;36m'; D='\033[2m'; 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
|
+
hr() { echo -e " ${D}$(printf '─%.0s' {1..50})${NC}"; }
|
|
61
|
+
die() { echo -e "\n${R}FATAL:${NC} $*" >&2; exit 1; }
|
|
62
|
+
|
|
63
|
+
prompt() {
|
|
64
|
+
# prompt <varname> <display-label> [default]
|
|
65
|
+
local var="$1" label="$2" default="${3:-}"
|
|
66
|
+
local hint=""
|
|
67
|
+
[[ -n "$default" ]] && hint=" ${D}[${default}]${NC}"
|
|
68
|
+
printf " %-28s%b: " "${label}" "${hint}"
|
|
69
|
+
read -r value </dev/tty
|
|
70
|
+
[[ -z "$value" && -n "$default" ]] && value="$default"
|
|
71
|
+
printf -v "$var" '%s' "$value"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
prompt_secret() {
|
|
75
|
+
local var="$1" label="$2"
|
|
76
|
+
printf " %-28s: " "${label}"
|
|
77
|
+
read -r -s value </dev/tty
|
|
78
|
+
echo ""
|
|
79
|
+
printf -v "$var" '%s' "$value"
|
|
80
|
+
}
|
|
62
81
|
|
|
63
82
|
# ── 0. Pre-flight ─────────────────────────────────────────────────────────────
|
|
64
83
|
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
|
-
|
|
84
|
+
banner "PLC Check-Weigher Installer v1.2"
|
|
85
|
+
[[ "${EUID}" -eq 0 ]] || die "Run via npx plc-checkweigher (asks for sudo password)"
|
|
86
|
+
[[ "$(uname -m)" == "aarch64" ]] || die "Requires 64-bit Raspberry Pi (aarch64). Got: $(uname -m)"
|
|
87
|
+
[[ -d "${HOME_DIR}" ]] || die "Home ${HOME_DIR} not found. Set PI_USER= to override."
|
|
88
|
+
command -v nmcli &>/dev/null || die "NetworkManager not found — install Raspberry Pi OS first."
|
|
72
89
|
info "Host : $(hostname)"
|
|
73
90
|
info "Kernel : $(uname -r)"
|
|
74
91
|
info "User : ${PI_USER}"
|
|
75
|
-
info "Repo : ${REPO_URL}"
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
# ── 1. System packages ────────────────────────────────────────────────────────
|
|
79
95
|
install_system_packages() {
|
|
80
|
-
step "
|
|
96
|
+
step "System packages ..."
|
|
81
97
|
DEBIAN_FRONTEND=noninteractive apt-get update -qq
|
|
82
98
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
|
83
99
|
git python3-venv python3-pip python3-dev \
|
|
84
100
|
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"
|
|
101
|
+
ok "git python3-venv samba-client cifs-utils build-essential"
|
|
140
102
|
}
|
|
141
103
|
|
|
142
|
-
# ──
|
|
104
|
+
# ── 2. Clone / update repo ────────────────────────────────────────────────────
|
|
143
105
|
setup_repo() {
|
|
144
|
-
step "
|
|
106
|
+
step "Repository ..."
|
|
145
107
|
if [[ -d "${INSTALL_DIR}/.git" ]]; then
|
|
146
108
|
sudo -u "${PI_USER}" git -C "${INSTALL_DIR}" pull --ff-only origin "${REPO_BRANCH}" \
|
|
147
109
|
&& ok "Repo updated → ${INSTALL_DIR}" \
|
|
@@ -153,41 +115,38 @@ setup_repo() {
|
|
|
153
115
|
fi
|
|
154
116
|
}
|
|
155
117
|
|
|
156
|
-
# ──
|
|
118
|
+
# ── 3. Python venv ────────────────────────────────────────────────────────────
|
|
157
119
|
setup_venv() {
|
|
158
|
-
step "
|
|
120
|
+
step "Python environment ..."
|
|
159
121
|
[[ -d "${VENV_DIR}" ]] \
|
|
160
122
|
&& ok "venv exists — skipping creation" \
|
|
161
123
|
|| sudo -u "${PI_USER}" python3 -m venv "${VENV_DIR}"
|
|
162
124
|
sudo -u "${PI_USER}" "${VENV_DIR}/bin/pip" install -q --upgrade pip
|
|
163
125
|
sudo -u "${PI_USER}" "${VENV_DIR}/bin/pip" install -q "${PY_PKGS[@]}"
|
|
164
|
-
ok "Packages installed
|
|
126
|
+
ok "Packages installed → ${VENV_DIR}"
|
|
165
127
|
}
|
|
166
128
|
|
|
167
|
-
# ──
|
|
129
|
+
# ── 4. Directories ────────────────────────────────────────────────────────────
|
|
168
130
|
setup_dirs() {
|
|
169
|
-
step "
|
|
131
|
+
step "Runtime directories ..."
|
|
170
132
|
mkdir -p "${REPORTS_DIR}"
|
|
171
133
|
chown "${PI_USER}:${PI_USER}" "${REPORTS_DIR}"
|
|
172
134
|
ok "${REPORTS_DIR}"
|
|
173
135
|
}
|
|
174
136
|
|
|
175
|
-
# ──
|
|
137
|
+
# ── 5. WiFi — scan → pick → password ─────────────────────────────────────────
|
|
176
138
|
setup_wifi() {
|
|
177
139
|
step "WiFi Setup"
|
|
178
140
|
|
|
179
|
-
# Check if wlan0 exists
|
|
180
141
|
if ! ip link show wlan0 &>/dev/null; then
|
|
181
|
-
warn "No wlan0
|
|
142
|
+
warn "No wlan0 found — skipping WiFi setup."
|
|
182
143
|
return
|
|
183
144
|
fi
|
|
184
145
|
|
|
185
|
-
echo ""
|
|
186
|
-
echo -e " ${C}Scanning for nearby networks ...${NC}"
|
|
146
|
+
echo -e "\n ${C}Scanning for networks ...${NC}"
|
|
187
147
|
nmcli dev wifi rescan ifname wlan0 2>/dev/null || true
|
|
188
148
|
sleep 3
|
|
189
149
|
|
|
190
|
-
# Build deduplicated, signal-sorted list [SSID, SIGNAL, SECURITY]
|
|
191
150
|
mapfile -t RAW < <(
|
|
192
151
|
nmcli -t -f SSID,SIGNAL,SECURITY dev wifi list ifname wlan0 2>/dev/null \
|
|
193
152
|
| grep -v '^:' \
|
|
@@ -197,65 +156,47 @@ setup_wifi() {
|
|
|
197
156
|
)
|
|
198
157
|
|
|
199
158
|
if [[ ${#RAW[@]} -eq 0 ]]; then
|
|
200
|
-
warn "No networks found
|
|
159
|
+
warn "No networks found — skipping WiFi setup."
|
|
201
160
|
return
|
|
202
161
|
fi
|
|
203
162
|
|
|
204
|
-
# ── Print menu ────────────────────────────────────────────────
|
|
205
163
|
echo ""
|
|
206
164
|
hr
|
|
207
165
|
printf " ${B}%-4s %-28s %-10s %s${NC}\n" "#" "SSID" "Signal" "Security"
|
|
208
166
|
hr
|
|
209
|
-
|
|
210
167
|
declare -a SSIDS SIGNALS SECURITIES
|
|
211
168
|
for i in "${!RAW[@]}"; do
|
|
212
169
|
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
|
|
170
|
+
SSIDS[$i]="${SSID}"; SIGNALS[$i]="${SIGNAL}"; SECURITIES[$i]="${SECURITY}"
|
|
218
171
|
SIG="${SIGNAL:-0}"
|
|
219
172
|
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}"
|
|
173
|
+
elif [[ $SIG -ge 60 ]]; then BAR="${G}▂▄▆ ${NC}"
|
|
174
|
+
elif [[ $SIG -ge 40 ]]; then BAR="${Y}▂▄ ${NC}"
|
|
175
|
+
else BAR="${R}▂ ${NC}"; fi
|
|
176
|
+
printf " %-4s %-28s %b %3s%% %s\n" \
|
|
177
|
+
"$((i+1)))" "${SSID}" "${BAR}" "${SIG}" "${SECURITY:---}"
|
|
227
178
|
done
|
|
228
|
-
|
|
229
179
|
hr
|
|
230
180
|
printf " %-4s %s\n" "0)" "Skip WiFi setup"
|
|
231
181
|
echo ""
|
|
232
182
|
|
|
233
|
-
# ── Read choice ───────────────────────────────────────────────
|
|
234
183
|
while true; do
|
|
235
184
|
read -r -p " Choose network [1-${#RAW[@]}] or 0 to skip: " CHOICE </dev/tty
|
|
236
185
|
[[ "$CHOICE" =~ ^[0-9]+$ ]] && \
|
|
237
|
-
[[ "$CHOICE" -ge 0
|
|
238
|
-
echo -e " ${R}
|
|
186
|
+
[[ "$CHOICE" -ge 0 && "$CHOICE" -le "${#RAW[@]}" ]] && break
|
|
187
|
+
echo -e " ${R}Enter a number between 0 and ${#RAW[@]}${NC}"
|
|
239
188
|
done
|
|
240
189
|
|
|
241
|
-
|
|
242
|
-
warn "WiFi setup skipped."
|
|
243
|
-
return
|
|
244
|
-
fi
|
|
190
|
+
[[ "$CHOICE" -eq 0 ]] && { warn "WiFi setup skipped."; return; }
|
|
245
191
|
|
|
246
192
|
IDX=$((CHOICE - 1))
|
|
247
193
|
SEL_SSID="${SSIDS[$IDX]}"
|
|
248
194
|
SEL_SEC="${SECURITIES[$IDX]}"
|
|
249
195
|
|
|
250
|
-
echo ""
|
|
251
|
-
ok "Selected: ${SEL_SSID}"
|
|
252
|
-
|
|
253
|
-
# ── Password prompt (skip for open networks) ──────────────────
|
|
254
196
|
WIFI_PASS=""
|
|
255
197
|
if [[ "${SEL_SEC}" != "--" && -n "${SEL_SEC}" ]]; then
|
|
256
198
|
while true; do
|
|
257
|
-
|
|
258
|
-
echo ""
|
|
199
|
+
prompt_secret WIFI_PASS "WiFi password"
|
|
259
200
|
[[ -n "${WIFI_PASS}" ]] && break
|
|
260
201
|
echo -e " ${R}Password cannot be empty for a secured network.${NC}"
|
|
261
202
|
done
|
|
@@ -263,42 +204,92 @@ setup_wifi() {
|
|
|
263
204
|
info "Open network — no password needed."
|
|
264
205
|
fi
|
|
265
206
|
|
|
266
|
-
# ── Create / update the NM connection profile ─────────────────
|
|
267
|
-
# Remove any existing profile with this SSID to avoid conflicts
|
|
268
207
|
nmcli connection delete "${SEL_SSID}" 2>/dev/null || true
|
|
269
|
-
|
|
270
208
|
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
|
|
209
|
+
nmcli connection add type wifi ifname wlan0 con-name "${SEL_SSID}" \
|
|
210
|
+
ssid "${SEL_SSID}" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "${WIFI_PASS}" \
|
|
211
|
+
connection.autoconnect yes connection.autoconnect-priority 200 2>/dev/null
|
|
279
212
|
else
|
|
280
|
-
nmcli connection add \
|
|
281
|
-
|
|
282
|
-
con-name "${SEL_SSID}" \
|
|
283
|
-
ssid "${SEL_SSID}" \
|
|
284
|
-
connection.autoconnect yes \
|
|
213
|
+
nmcli connection add type wifi ifname wlan0 con-name "${SEL_SSID}" \
|
|
214
|
+
ssid "${SEL_SSID}" connection.autoconnect yes \
|
|
285
215
|
connection.autoconnect-priority 200 2>/dev/null
|
|
286
216
|
fi
|
|
287
217
|
|
|
288
|
-
# Connect now so the rest of the setup (git pull, pip) works
|
|
289
218
|
echo -n " Connecting ..."
|
|
290
219
|
if nmcli connection up "${SEL_SSID}" 2>/dev/null; then
|
|
291
|
-
echo ""
|
|
292
|
-
ok "Connected to '${SEL_SSID}'"
|
|
220
|
+
echo ""; ok "Connected to '${SEL_SSID}'"
|
|
293
221
|
else
|
|
222
|
+
echo ""; warn "Will connect automatically after reboot."
|
|
223
|
+
fi
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# ── 6. SMB file sharing — interactive ────────────────────────────────────────
|
|
227
|
+
setup_smb() {
|
|
228
|
+
step "SMB File Sharing Setup"
|
|
229
|
+
echo ""
|
|
230
|
+
echo -e " ${C}PDF reports will be pushed to a shared folder on another PC.${NC}"
|
|
231
|
+
echo -e " ${D}Leave blank to disable SMB push.${NC}"
|
|
232
|
+
echo ""
|
|
233
|
+
hr
|
|
234
|
+
|
|
235
|
+
prompt SMB_HOST "Host IP address" ""
|
|
236
|
+
if [[ -z "${SMB_HOST}" ]]; then
|
|
237
|
+
warn "SMB push disabled — no host entered."
|
|
238
|
+
# Write a disabled smb_config.py
|
|
239
|
+
cat > "${INSTALL_DIR}/smb_config.py" << 'EOF'
|
|
240
|
+
# SMB push disabled during setup
|
|
241
|
+
SMB_ENABLED = False
|
|
242
|
+
SMB_HOST = ""
|
|
243
|
+
SMB_SHARE = ""
|
|
244
|
+
SMB_USERNAME = ""
|
|
245
|
+
SMB_PASSWORD = ""
|
|
246
|
+
SMB_SUBDIR = ""
|
|
247
|
+
EOF
|
|
248
|
+
chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/smb_config.py"
|
|
249
|
+
return
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
prompt SMB_SHARE "Share name (folder)" "Reports"
|
|
253
|
+
prompt SMB_USERNAME "Username" ""
|
|
254
|
+
prompt_secret SMB_PASSWORD "Password"
|
|
255
|
+
prompt SMB_SUBDIR "Subfolder (optional)" ""
|
|
256
|
+
|
|
257
|
+
hr
|
|
258
|
+
echo ""
|
|
259
|
+
|
|
260
|
+
# Write smb_config.py (gitignored — credentials stay off GitHub)
|
|
261
|
+
cat > "${INSTALL_DIR}/smb_config.py" << EOF
|
|
262
|
+
# SMB configuration — written by setup.sh, NOT committed to git.
|
|
263
|
+
SMB_ENABLED = True
|
|
264
|
+
SMB_HOST = "${SMB_HOST}"
|
|
265
|
+
SMB_SHARE = "${SMB_SHARE}"
|
|
266
|
+
SMB_USERNAME = "${SMB_USERNAME}"
|
|
267
|
+
SMB_PASSWORD = "${SMB_PASSWORD}"
|
|
268
|
+
SMB_SUBDIR = "${SMB_SUBDIR}"
|
|
269
|
+
EOF
|
|
270
|
+
chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/smb_config.py"
|
|
271
|
+
ok "SMB config saved → ${INSTALL_DIR}/smb_config.py"
|
|
272
|
+
|
|
273
|
+
# Test connectivity
|
|
274
|
+
echo -n " Testing connection to ${SMB_HOST} ..."
|
|
275
|
+
if ping -c 2 -W 2 "${SMB_HOST}" &>/dev/null; then
|
|
294
276
|
echo ""
|
|
295
|
-
|
|
277
|
+
ok "Host ${SMB_HOST} reachable"
|
|
278
|
+
echo -n " Authenticating with share //${SMB_HOST}/${SMB_SHARE} ..."
|
|
279
|
+
if smbclient "//${SMB_HOST}/${SMB_SHARE}" \
|
|
280
|
+
-U "${SMB_USERNAME}%${SMB_PASSWORD}" -c "ls" &>/dev/null 2>&1; then
|
|
281
|
+
echo ""; ok "SMB share authenticated — PDF push is ready"
|
|
282
|
+
else
|
|
283
|
+
echo ""; warn "Auth failed — verify credentials and share name after reboot."
|
|
284
|
+
fi
|
|
285
|
+
else
|
|
286
|
+
echo ""; warn "${SMB_HOST} not reachable now — will retry at runtime."
|
|
296
287
|
fi
|
|
297
288
|
}
|
|
298
289
|
|
|
299
290
|
# ── 7. Network-online guarantee ───────────────────────────────────────────────
|
|
300
291
|
setup_network_online() {
|
|
301
|
-
step "
|
|
292
|
+
step "NetworkManager-wait-online ..."
|
|
302
293
|
mkdir -p /etc/systemd/system/NetworkManager-wait-online.service.d/
|
|
303
294
|
cat > /etc/systemd/system/NetworkManager-wait-online.service.d/timeout.conf << 'EOF'
|
|
304
295
|
[Service]
|
|
@@ -306,14 +297,13 @@ ExecStart=
|
|
|
306
297
|
ExecStart=/usr/lib/NetworkManager/nm-online -s -q --timeout=60
|
|
307
298
|
EOF
|
|
308
299
|
systemctl enable NetworkManager-wait-online.service 2>/dev/null || true
|
|
309
|
-
ok "Services will wait up to 60 s for
|
|
300
|
+
ok "Services will wait up to 60 s for network on each boot"
|
|
310
301
|
}
|
|
311
302
|
|
|
312
|
-
# ── 8
|
|
303
|
+
# ── 8. systemd services ───────────────────────────────────────────────────────
|
|
313
304
|
install_services() {
|
|
314
|
-
step "
|
|
305
|
+
step "systemd services ..."
|
|
315
306
|
|
|
316
|
-
# plc_watcher — SCHED_FIFO:50, IOClass=realtime, pinned to CPU core 3
|
|
317
307
|
cat > /etc/systemd/system/plc_watcher.service << EOF
|
|
318
308
|
[Unit]
|
|
319
309
|
Description=PLC Check-Weigher Start Watcher
|
|
@@ -342,7 +332,6 @@ WantedBy=multi-user.target
|
|
|
342
332
|
EOF
|
|
343
333
|
ok "plc_watcher.service (SCHED_FIFO:50 · IOClass=realtime · CPU core 3)"
|
|
344
334
|
|
|
345
|
-
# plc_web — elevated but not real-time
|
|
346
335
|
cat > /etc/systemd/system/plc_web.service << EOF
|
|
347
336
|
[Unit]
|
|
348
337
|
Description=PLC Check-Weigher Report Viewer
|
|
@@ -369,38 +358,88 @@ EOF
|
|
|
369
358
|
|
|
370
359
|
systemctl daemon-reload
|
|
371
360
|
systemctl enable plc_watcher.service plc_web.service
|
|
372
|
-
ok "Both services enabled —
|
|
361
|
+
ok "Both services enabled — start automatically after reboot"
|
|
373
362
|
|
|
374
|
-
# Mirror updated service file back into the repo
|
|
375
363
|
cp /etc/systemd/system/plc_watcher.service "${INSTALL_DIR}/plc_watcher.service"
|
|
376
364
|
chown "${PI_USER}:${PI_USER}" "${INSTALL_DIR}/plc_watcher.service"
|
|
377
365
|
}
|
|
378
366
|
|
|
367
|
+
# ── 9. RT kernel — installed LAST so only one reboot is needed ────────────────
|
|
368
|
+
install_rt_kernel() {
|
|
369
|
+
step "PREEMPT_RT kernel (final step before reboot) ..."
|
|
370
|
+
|
|
371
|
+
if grep -q "PREEMPT_RT" /proc/version 2>/dev/null; then
|
|
372
|
+
ok "Already running PREEMPT_RT — skipping kernel install"
|
|
373
|
+
return
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
# Backup stock kernel
|
|
377
|
+
if [[ ! -f "${BOOT_FW}/kernel8-stock.img" ]]; then
|
|
378
|
+
cp "${BOOT_FW}/kernel8.img" "${BOOT_FW}/kernel8-stock.img"
|
|
379
|
+
ok "Stock kernel backed up → kernel8-stock.img"
|
|
380
|
+
fi
|
|
381
|
+
|
|
382
|
+
CHKSUM_BEFORE="$(md5sum "${BOOT_FW}/kernel8.img" | cut -d' ' -f1)"
|
|
383
|
+
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${RT_PKG}" "${RT_HDR}"
|
|
384
|
+
CHKSUM_AFTER="$(md5sum "${BOOT_FW}/kernel8.img" | cut -d' ' -f1)"
|
|
385
|
+
|
|
386
|
+
if [[ "${CHKSUM_BEFORE}" != "${CHKSUM_AFTER}" ]]; then
|
|
387
|
+
cp "${BOOT_FW}/kernel8.img" "${BOOT_FW}/kernel8-rt.img"
|
|
388
|
+
cp "${BOOT_FW}/kernel8-stock.img" "${BOOT_FW}/kernel8.img"
|
|
389
|
+
ok "RT kernel → kernel8-rt.img | stock restored as boot default"
|
|
390
|
+
else
|
|
391
|
+
RT_VMLINUZ="$(ls /boot/vmlinuz-*rt-arm64 2>/dev/null | sort -V | tail -1)"
|
|
392
|
+
[[ -n "${RT_VMLINUZ}" ]] || die "RT vmlinuz not found in /boot/"
|
|
393
|
+
if file "${RT_VMLINUZ}" | grep -q gzip; then
|
|
394
|
+
zcat "${RT_VMLINUZ}" > "${BOOT_FW}/kernel8-rt.img"
|
|
395
|
+
else
|
|
396
|
+
cp "${RT_VMLINUZ}" "${BOOT_FW}/kernel8-rt.img"
|
|
397
|
+
fi
|
|
398
|
+
ok "RT kernel manually copied → kernel8-rt.img"
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
RT_INITRD="$(ls /boot/initrd.img-*rt-arm64 2>/dev/null | sort -V | tail -1 || true)"
|
|
402
|
+
[[ -n "${RT_INITRD}" ]] && cp "${RT_INITRD}" "${BOOT_FW}/initramfs8-rt" \
|
|
403
|
+
&& ok "RT initramfs → initramfs8-rt"
|
|
404
|
+
|
|
405
|
+
# Activate in config.txt (idempotent)
|
|
406
|
+
sed -i '/### PLC-RT-BLOCK-START ###/,/### PLC-RT-BLOCK-END ###/d' \
|
|
407
|
+
"${BOOT_FW}/config.txt"
|
|
408
|
+
cat >> "${BOOT_FW}/config.txt" << 'EOF'
|
|
409
|
+
|
|
410
|
+
### PLC-RT-BLOCK-START ###
|
|
411
|
+
# PREEMPT_RT kernel — installed by plc-checkweigher setup.sh
|
|
412
|
+
# To revert to stock: comment the two lines below and reboot.
|
|
413
|
+
kernel=kernel8-rt.img
|
|
414
|
+
initramfs initramfs8-rt followkernel
|
|
415
|
+
### PLC-RT-BLOCK-END ###
|
|
416
|
+
EOF
|
|
417
|
+
ok "config.txt updated — RT kernel activates on reboot"
|
|
418
|
+
}
|
|
419
|
+
|
|
379
420
|
# ── Summary + countdown reboot ────────────────────────────────────────────────
|
|
380
421
|
do_reboot() {
|
|
381
422
|
echo ""
|
|
382
|
-
banner "
|
|
423
|
+
banner "Setup Complete"
|
|
383
424
|
echo ""
|
|
384
|
-
|
|
385
|
-
printf " %-
|
|
386
|
-
printf " %-
|
|
387
|
-
printf " %-
|
|
388
|
-
printf " %-
|
|
389
|
-
printf " %-
|
|
390
|
-
printf " %-30s %s\n" "Web dashboard (after reboot):" \
|
|
425
|
+
printf " ${G}%-32s${NC} %s\n" "Repo:" "${INSTALL_DIR}"
|
|
426
|
+
printf " ${G}%-32s${NC} %s\n" "Python venv:" "${VENV_DIR}"
|
|
427
|
+
printf " ${G}%-32s${NC} %s\n" "Reports output:" "${REPORTS_DIR}"
|
|
428
|
+
printf " ${G}%-32s${NC} %s\n" "RT kernel:" "kernel8-rt.img (active after reboot)"
|
|
429
|
+
printf " ${G}%-32s${NC} %s\n" "Stock kernel fallback:" "kernel8-stock.img"
|
|
430
|
+
printf " ${G}%-32s${NC} %s\n" "Web dashboard (after reboot):" \
|
|
391
431
|
"http://$(hostname -I | awk '{print $1}' 2>/dev/null || echo '<pi-ip>'):8080"
|
|
392
|
-
|
|
393
432
|
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)"
|
|
433
|
+
echo -e " ${Y}After reboot:${NC}"
|
|
434
|
+
echo " journalctl -u plc_watcher -f # live logs"
|
|
435
|
+
echo " sudo chrt -p \$(systemctl show -p MainPID --value plc_watcher) # verify RT"
|
|
397
436
|
echo ""
|
|
398
437
|
|
|
399
438
|
echo -e "${G}"
|
|
400
439
|
echo " ╔══════════════════════════════════════════════════════╗"
|
|
401
|
-
echo " ║ Rebooting in 10 seconds to apply all
|
|
402
|
-
echo " ║
|
|
403
|
-
echo " ║
|
|
440
|
+
echo " ║ All done. Rebooting in 10 seconds to apply all ║"
|
|
441
|
+
echo " ║ changes including the RT kernel. ║"
|
|
442
|
+
echo " ║ Press Ctrl+C to cancel — then: sudo reboot ║"
|
|
404
443
|
echo " ╚══════════════════════════════════════════════════════╝"
|
|
405
444
|
echo -e "${NC}"
|
|
406
445
|
|
|
@@ -415,15 +454,16 @@ do_reboot() {
|
|
|
415
454
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
416
455
|
main() {
|
|
417
456
|
preflight
|
|
418
|
-
install_system_packages
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
setup_network_online
|
|
425
|
-
install_services
|
|
426
|
-
|
|
457
|
+
install_system_packages # 1
|
|
458
|
+
setup_repo # 2
|
|
459
|
+
setup_venv # 3
|
|
460
|
+
setup_dirs # 4
|
|
461
|
+
setup_wifi # 5 — interactive WiFi picker
|
|
462
|
+
setup_smb # 6 — interactive SMB config → smb_config.py
|
|
463
|
+
setup_network_online # 7
|
|
464
|
+
install_services # 8
|
|
465
|
+
install_rt_kernel # 9 — LAST, so only one reboot needed
|
|
466
|
+
do_reboot # 10 — single reboot applies everything
|
|
427
467
|
}
|
|
428
468
|
|
|
429
469
|
main "$@"
|