plc-checkweigher 1.7.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +150 -41
- package/bin/plc_checkweigher +314 -113
- package/package.json +2 -1
- package/uninstall.sh +203 -0
package/bin/cli.js
CHANGED
|
@@ -6,61 +6,170 @@ const { platform, arch } = require('os');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
|
|
9
|
-
// ──
|
|
9
|
+
// ── Colours ───────────────────────────────────────────────────────────────────
|
|
10
10
|
const B = '\x1b[1;34m'; // bold blue
|
|
11
11
|
const G = '\x1b[0;32m'; // green
|
|
12
12
|
const R = '\x1b[1;31m'; // red
|
|
13
13
|
const Y = '\x1b[1;33m'; // yellow
|
|
14
|
-
const
|
|
14
|
+
const D = '\x1b[2m'; // dim
|
|
15
|
+
const NC = '\x1b[0m'; // reset
|
|
15
16
|
|
|
16
17
|
function die(msg) {
|
|
17
18
|
console.error(`\n${R}Error:${NC} ${msg}`);
|
|
18
19
|
process.exit(1);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
// ──
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// ── Dot-matrix font (5 px wide × 5 px tall, █ = lit, space = dark) ──────────
|
|
23
|
+
const GLYPHS = {
|
|
24
|
+
'T': ['█████', ' █ ', ' █ ', ' █ ', ' █ '],
|
|
25
|
+
'Ø': [' ███ ', '█ /█', '█ / █', '█/ █', ' ███ '],
|
|
26
|
+
'V': ['█ █', '█ █', ' █ █ ', ' █ █ ', ' █ '],
|
|
27
|
+
'E': ['█████', '█ ', '████ ', '█ ', '█████'],
|
|
28
|
+
'X': ['█ █', ' █ █ ', ' █ ', ' █ █ ', '█ █'],
|
|
29
|
+
'S': [' ████', '█ ', ' ███ ', ' █', '████ '],
|
|
30
|
+
'Y': ['█ █', ' █ █ ', ' █ ', ' █ ', ' █ '],
|
|
31
|
+
'M': ['█ █', '██ ██', '█ █ █', '█ █', '█ █'],
|
|
32
|
+
' ': [' ', ' ', ' ', ' ', ' '],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns 5 equal-length strings representing the dot-matrix rows of `word`.
|
|
37
|
+
* Each character glyph is 5 wide; glyphs are separated by a single space.
|
|
38
|
+
*/
|
|
39
|
+
function dotRows(word) {
|
|
40
|
+
const rows = ['', '', '', '', ''];
|
|
41
|
+
for (const ch of word.toUpperCase()) {
|
|
42
|
+
const g = GLYPHS[ch] || GLYPHS[' '];
|
|
43
|
+
for (let i = 0; i < 5; i++) rows[i] += g[i] + ' ';
|
|
44
|
+
}
|
|
45
|
+
// Remove the one trailing separator space added after the last glyph
|
|
46
|
+
return rows.map(r => r.slice(0, -1));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── TØVEX-SYSTEMS access banner ───────────────────────────────────────────────
|
|
50
|
+
function showAccessDenied() {
|
|
51
|
+
const INNER = 52; // characters between the ║ borders
|
|
52
|
+
|
|
53
|
+
// Center a plain-text string inside INNER
|
|
54
|
+
function cen(str) {
|
|
55
|
+
const len = str.length;
|
|
56
|
+
const lpad = Math.floor((INNER - len) / 2);
|
|
57
|
+
const rpad = INNER - len - lpad;
|
|
58
|
+
return ' '.repeat(Math.max(0, lpad)) + str + ' '.repeat(Math.max(0, rpad));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const bar = '═'.repeat(INNER);
|
|
62
|
+
const blank = `${B}║${' '.repeat(INNER)}║${NC}`;
|
|
63
|
+
|
|
64
|
+
function boxRow(str, color) {
|
|
65
|
+
return `${B}║${NC}${color}${cen(str)}${NC}${B}║${NC}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const tRow = dotRows('TØVEX');
|
|
69
|
+
const sRow = dotRows('SYSTEMS');
|
|
70
|
+
|
|
71
|
+
// Dot-separator exactly as wide as SYSTEMS
|
|
72
|
+
const sep = Array.from({ length: sRow[0].length }, (_, i) => i % 2 ? ' ' : '·').join('');
|
|
73
|
+
|
|
74
|
+
// ── Print box ─────────────────────────────────────────────────────────────
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log(`${B}╔${bar}╗${NC}`);
|
|
77
|
+
console.log(blank);
|
|
78
|
+
for (const r of tRow) console.log(boxRow(r, B));
|
|
79
|
+
console.log(blank);
|
|
80
|
+
console.log(boxRow(sep, D));
|
|
81
|
+
console.log(blank);
|
|
82
|
+
for (const r of sRow) console.log(boxRow(r, B));
|
|
83
|
+
console.log(blank);
|
|
84
|
+
console.log(`${B}╚${bar}╝${NC}`);
|
|
85
|
+
|
|
86
|
+
// ── Access-denied message ─────────────────────────────────────────────────
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(` ${R}⚠ Please contact administrator for access${NC}`);
|
|
89
|
+
console.log('');
|
|
90
|
+
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Argument parsing ──────────────────────────────────────────────────────────
|
|
95
|
+
const arg = (process.argv[2] || '').trim();
|
|
96
|
+
|
|
97
|
+
const INSTALL_FLAGS = ['-tov', '--install', 'install'];
|
|
98
|
+
const UNINSTALL_FLAGS = ['-ex', '--uninstall', 'uninstall', 'remove'];
|
|
99
|
+
const HELP_FLAGS = ['-h', '--help', 'help'];
|
|
100
|
+
|
|
101
|
+
if (HELP_FLAGS.includes(arg)) {
|
|
102
|
+
showAccessDenied();
|
|
24
103
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
104
|
+
|
|
105
|
+
let mode = 'access'; // default: show brand banner + access denied
|
|
106
|
+
if (INSTALL_FLAGS.includes(arg)) mode = 'install';
|
|
107
|
+
else if (UNINSTALL_FLAGS.includes(arg)) mode = 'uninstall';
|
|
108
|
+
else if (arg !== '') showAccessDenied();
|
|
109
|
+
|
|
110
|
+
// ── Platform guards (skip for help / access-denied) ───────────────────────────
|
|
111
|
+
if (mode !== 'access') {
|
|
112
|
+
if (platform() !== 'linux')
|
|
113
|
+
die('This installer only runs on Raspberry Pi (Linux). Got: ' + platform());
|
|
114
|
+
if (arch() !== 'arm64')
|
|
115
|
+
die('Requires 64-bit ARM (arm64). Got: ' + arch() +
|
|
116
|
+
'\nMake sure you are running 64-bit Raspberry Pi OS.');
|
|
28
117
|
}
|
|
29
118
|
|
|
30
|
-
// ──
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
119
|
+
// ── Locate scripts ────────────────────────────────────────────────────────────
|
|
120
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
121
|
+
const setupScript = path.join(pkgRoot, 'setup.sh');
|
|
122
|
+
const uninstallScript = path.join(pkgRoot, 'uninstall.sh');
|
|
123
|
+
|
|
124
|
+
// ── Dispatch ──────────────────────────────────────────────────────────────────
|
|
125
|
+
if (mode === 'access') {
|
|
126
|
+
showAccessDenied(); // exits with code 1
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (mode === 'install') {
|
|
130
|
+
if (!fs.existsSync(setupScript))
|
|
131
|
+
die('setup.sh not found — try reinstalling the package: npx plc-checkweigher@latest');
|
|
132
|
+
|
|
133
|
+
console.log(`
|
|
134
|
+
${B}╔══════════════════════════════════════════════╗
|
|
135
|
+
║ PLC Check-Weigher — Full Stack Installer ║
|
|
136
|
+
╚══════════════════════════════════════════════╝${NC}
|
|
35
137
|
`);
|
|
138
|
+
console.log(`${Y}This will:${NC}`);
|
|
139
|
+
console.log(' 1. Install the PREEMPT_RT real-time kernel (reboots once)');
|
|
140
|
+
console.log(' 2. Install all Python dependencies');
|
|
141
|
+
console.log(' 3. Clone / update the plc-checkweigher repo');
|
|
142
|
+
console.log(' 4. Configure WiFi, SMB file sharing (credentials → smb_config.py)');
|
|
143
|
+
console.log(' 5. Install systemd services with RT scheduling priority');
|
|
144
|
+
console.log(' 6. Set up live dashboard → http://<pi-ip>:8080/live');
|
|
145
|
+
console.log(' 7. Set up PDF report viewer with instant auto-refresh');
|
|
146
|
+
console.log('');
|
|
147
|
+
console.log(`${Y}Sudo password required to make system-level changes.${NC}`);
|
|
148
|
+
console.log('');
|
|
36
149
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
150
|
+
const result = spawnSync('sudo', ['bash', setupScript], {
|
|
151
|
+
stdio: 'inherit',
|
|
152
|
+
env: process.env,
|
|
153
|
+
});
|
|
154
|
+
process.exit(result.status ?? 0);
|
|
41
155
|
}
|
|
42
156
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
console.log(
|
|
53
|
-
console.log(`${
|
|
54
|
-
console.log('');
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
stdio: 'inherit',
|
|
63
|
-
env: process.env,
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
process.exit(result.status ?? 0);
|
|
157
|
+
if (mode === 'uninstall') {
|
|
158
|
+
if (!fs.existsSync(uninstallScript))
|
|
159
|
+
die('uninstall.sh not found — try reinstalling the package: npx plc-checkweigher@latest');
|
|
160
|
+
|
|
161
|
+
console.log(`
|
|
162
|
+
${R}╔══════════════════════════════════════════════╗
|
|
163
|
+
║ PLC Check-Weigher — Uninstaller ║
|
|
164
|
+
╚══════════════════════════════════════════════╝${NC}
|
|
165
|
+
`);
|
|
166
|
+
console.log(`${D}Removes all services, code, venv, kernel config, and CLI tools.${NC}`);
|
|
167
|
+
console.log(`${D}You will be asked whether to keep your PDF reports.${NC}`);
|
|
168
|
+
console.log('');
|
|
169
|
+
|
|
170
|
+
const result = spawnSync('sudo', ['bash', uninstallScript], {
|
|
171
|
+
stdio: 'inherit',
|
|
172
|
+
env: process.env,
|
|
173
|
+
});
|
|
174
|
+
process.exit(result.status ?? 0);
|
|
175
|
+
}
|
package/bin/plc_checkweigher
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# plc_checkweigher — CLI for the PLC Check-Weigher system
|
|
3
3
|
# Installed to /usr/local/bin/ by setup.sh
|
|
4
|
-
#
|
|
5
|
-
# Commands:
|
|
6
|
-
# status Full system diagnostic
|
|
7
|
-
# logs Live journalctl stream
|
|
8
|
-
# restart / start / stop Service control
|
|
9
|
-
# push-test Push latest PDF to SMB target
|
|
10
|
-
# queue Show SMB delivery queue + ledger
|
|
11
|
-
# wifi Scan and switch WiFi network (prompts SMB IP update)
|
|
12
|
-
# hotspot on/off/status/scan WiFi hotspot (AP mode) — direct PC connection
|
|
13
|
-
# display on/off/status Enable/disable the display (LightDM)
|
|
14
|
-
# smb-config Interactively update SMB delivery target
|
|
15
4
|
|
|
16
5
|
set -euo pipefail
|
|
17
6
|
|
|
@@ -19,6 +8,9 @@ INSTALL_DIR="/home/pi/plc_checkweigher"
|
|
|
19
8
|
PYTHON="/home/pi/plc_env/bin/python3"
|
|
20
9
|
SMB_CFG="${INSTALL_DIR}/smb_config.py"
|
|
21
10
|
|
|
11
|
+
# ── TTY detection — animations only when connected to a real terminal ─────────
|
|
12
|
+
[[ -t 1 ]] && _TTY=1 || _TTY=0
|
|
13
|
+
|
|
22
14
|
# ── Colours ───────────────────────────────────────────────────────────────────
|
|
23
15
|
B='\033[1;34m'; G='\033[0;32m'; R='\033[1;31m'; Y='\033[1;33m'
|
|
24
16
|
C='\033[0;36m'; D='\033[2m'; W='\033[1m'; NC='\033[0m'
|
|
@@ -28,33 +20,72 @@ err() { echo -e " ${R}✗${NC} $*" >&2; }
|
|
|
28
20
|
warn() { echo -e " ${Y}!${NC} $*"; }
|
|
29
21
|
info() { echo -e " ${C}i${NC} $*"; }
|
|
30
22
|
hr() { echo -e " ${D}$(printf '─%.0s' {1..56})${NC}"; }
|
|
31
|
-
banner() { echo -e "\n${B} ▸ $*${NC}"; }
|
|
32
23
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
# ── Typewrite banner ──────────────────────────────────────────────────────────
|
|
25
|
+
banner() {
|
|
26
|
+
echo ""
|
|
27
|
+
if [[ $_TTY -eq 1 ]]; then
|
|
28
|
+
printf " ${B}▸ ${NC}"
|
|
29
|
+
local txt="$*" i
|
|
30
|
+
for ((i=0; i<${#txt}; i++)); do
|
|
31
|
+
printf "${B}%s${NC}" "${txt:$i:1}"
|
|
32
|
+
sleep 0.022
|
|
33
|
+
done
|
|
34
|
+
echo ""
|
|
35
|
+
else
|
|
36
|
+
echo -e "${B} ▸ $*${NC}"
|
|
38
37
|
fi
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
# ──
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
# ── Spinner ───────────────────────────────────────────────────────────────────
|
|
41
|
+
_SP_PID=""
|
|
42
|
+
_SP_MSG=""
|
|
43
|
+
_SP_FRAMES=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
|
|
44
|
+
|
|
45
|
+
_spin_kill() {
|
|
46
|
+
if [[ -n "$_SP_PID" ]]; then
|
|
47
|
+
kill "$_SP_PID" 2>/dev/null || true
|
|
48
|
+
wait "$_SP_PID" 2>/dev/null || true
|
|
49
|
+
_SP_PID=""
|
|
50
|
+
fi
|
|
51
|
+
if [[ $_TTY -eq 1 ]]; then printf '\r\033[K'; fi
|
|
52
|
+
return 0
|
|
53
|
+
}
|
|
54
|
+
trap '_spin_kill' EXIT INT TERM
|
|
55
|
+
|
|
56
|
+
spin_start() {
|
|
57
|
+
_SP_MSG="${1:-}"
|
|
58
|
+
[[ $_TTY -eq 0 ]] && { echo -e " ${C}…${NC} ${_SP_MSG}"; return; }
|
|
59
|
+
_spin_kill
|
|
60
|
+
(
|
|
61
|
+
local i=0
|
|
62
|
+
while true; do
|
|
63
|
+
printf "\r ${C}%s${NC} %s " "${_SP_FRAMES[$((i % 10))]}" "${_SP_MSG}"
|
|
64
|
+
sleep 0.08
|
|
65
|
+
i=$((i + 1))
|
|
66
|
+
done
|
|
67
|
+
) &
|
|
68
|
+
_SP_PID=$!
|
|
46
69
|
}
|
|
47
70
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
spin_ok() { local m="${_SP_MSG}"; _spin_kill; ok "${m}${1:+ ${D}$1${NC}}"; return 0; }
|
|
72
|
+
spin_warn() { local m="${_SP_MSG}"; _spin_kill; warn "${m}${1:+ ${D}$1${NC}}"; return 0; }
|
|
73
|
+
spin_err() { local m="${_SP_MSG}"; _spin_kill; err "${m}${1:+ ${D}$1${NC}}"; return 0; }
|
|
74
|
+
|
|
75
|
+
# ── sudo helper ───────────────────────────────────────────────────────────────
|
|
76
|
+
need_sudo() {
|
|
77
|
+
if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
|
|
78
|
+
echo -e " ${Y}sudo password required${NC}"
|
|
79
|
+
sudo -v
|
|
54
80
|
fi
|
|
55
81
|
}
|
|
56
82
|
|
|
57
|
-
# ──
|
|
83
|
+
# ── Read / write smb_config.py ────────────────────────────────────────────────
|
|
84
|
+
smb_get() {
|
|
85
|
+
grep "^${1}" "${SMB_CFG}" 2>/dev/null | head -1 | sed 's/.*= *"//;s/".*//'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# ── SMB config update prompt (reused by wifi + smb-config + hotspot scan) ────
|
|
58
89
|
prompt_smb_config() {
|
|
59
90
|
local context="${1:-}"
|
|
60
91
|
echo ""
|
|
@@ -62,34 +93,26 @@ prompt_smb_config() {
|
|
|
62
93
|
[[ -n "$context" ]] && info "$context"
|
|
63
94
|
echo ""
|
|
64
95
|
|
|
96
|
+
local CUR_HOST CUR_SHARE CUR_USER CUR_PASS
|
|
65
97
|
CUR_HOST=$(smb_get "SMB_HOST")
|
|
66
98
|
CUR_SHARE=$(smb_get "SMB_SHARE")
|
|
67
99
|
CUR_USER=$(smb_get "SMB_USERNAME")
|
|
68
100
|
CUR_PASS=$(smb_get "SMB_PASSWORD")
|
|
69
|
-
CUR_ENABLED=$(grep "^SMB_ENABLED" "${SMB_CFG}" 2>/dev/null | grep -o "True\|False" || echo "True")
|
|
70
101
|
|
|
71
102
|
hr
|
|
72
|
-
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Host IP"
|
|
73
|
-
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Share name"
|
|
74
|
-
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Username"
|
|
75
|
-
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Password"
|
|
103
|
+
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Host IP" "${CUR_HOST:-(not set)}"
|
|
104
|
+
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Share name" "${CUR_SHARE:-(not set)}"
|
|
105
|
+
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Username" "${CUR_USER:-(not set)}"
|
|
106
|
+
printf " ${B}%-22s${NC} ${D}current: %s${NC}\n" "Password" "${CUR_PASS:-(not set)}"
|
|
76
107
|
hr
|
|
77
108
|
echo ""
|
|
78
109
|
|
|
79
|
-
|
|
80
|
-
NEW_HOST="${NEW_HOST:-$CUR_HOST}"
|
|
110
|
+
local NEW_HOST NEW_SHARE NEW_USER NEW_PASS
|
|
111
|
+
read -r -p " Host IP [${CUR_HOST}]: " NEW_HOST </dev/tty; NEW_HOST="${NEW_HOST:-$CUR_HOST}"
|
|
112
|
+
read -r -p " Share name [${CUR_SHARE:-Reports}]: " NEW_SHARE </dev/tty; NEW_SHARE="${NEW_SHARE:-${CUR_SHARE:-Reports}}"
|
|
113
|
+
read -r -p " Username [${CUR_USER:-plcreport}]: " NEW_USER </dev/tty; NEW_USER="${NEW_USER:-${CUR_USER:-plcreport}}"
|
|
114
|
+
read -r -s -p " Password (blank = keep current): " NEW_PASS </dev/tty; echo ""; NEW_PASS="${NEW_PASS:-$CUR_PASS}"
|
|
81
115
|
|
|
82
|
-
read -r -p " Share name [${CUR_SHARE:-Reports}]: " NEW_SHARE </dev/tty
|
|
83
|
-
NEW_SHARE="${NEW_SHARE:-${CUR_SHARE:-Reports}}"
|
|
84
|
-
|
|
85
|
-
read -r -p " Username [${CUR_USER:-plcreport}]: " NEW_USER </dev/tty
|
|
86
|
-
NEW_USER="${NEW_USER:-${CUR_USER:-plcreport}}"
|
|
87
|
-
|
|
88
|
-
read -r -s -p " Password (leave blank to keep current): " NEW_PASS </dev/tty
|
|
89
|
-
echo ""
|
|
90
|
-
NEW_PASS="${NEW_PASS:-$CUR_PASS}"
|
|
91
|
-
|
|
92
|
-
# Write updated config
|
|
93
116
|
cat > "${SMB_CFG}" << EOF
|
|
94
117
|
SMB_ENABLED = True
|
|
95
118
|
SMB_HOST = "${NEW_HOST}"
|
|
@@ -101,22 +124,21 @@ EOF
|
|
|
101
124
|
|
|
102
125
|
echo ""
|
|
103
126
|
ok "smb_config.py updated"
|
|
104
|
-
info "
|
|
105
|
-
|
|
106
|
-
# Quick connectivity test
|
|
127
|
+
info "Target: //${NEW_HOST}/${NEW_SHARE} as ${NEW_USER}"
|
|
107
128
|
echo ""
|
|
108
|
-
|
|
129
|
+
|
|
130
|
+
spin_start "Pinging ${NEW_HOST}"
|
|
109
131
|
if ping -c 2 -W 2 "${NEW_HOST}" &>/dev/null; then
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if smbclient "//${NEW_HOST}/${NEW_SHARE}"
|
|
113
|
-
|
|
132
|
+
spin_ok "${NEW_HOST} reachable"
|
|
133
|
+
spin_start "Authenticating with //${NEW_HOST}/${NEW_SHARE}"
|
|
134
|
+
if smbclient "//${NEW_HOST}/${NEW_SHARE}" \
|
|
135
|
+
-U "${NEW_USER}%${NEW_PASS}" -c "ls" &>/dev/null 2>&1; then
|
|
136
|
+
spin_ok "SMB auth OK — PDF delivery ready"
|
|
114
137
|
else
|
|
115
|
-
|
|
138
|
+
spin_warn "Auth failed — verify share name and credentials on the PC"
|
|
116
139
|
fi
|
|
117
140
|
else
|
|
118
|
-
|
|
119
|
-
warn "${NEW_HOST} not reachable right now — config saved, will retry at runtime"
|
|
141
|
+
spin_warn "${NEW_HOST} not reachable — config saved, will retry at runtime"
|
|
120
142
|
fi
|
|
121
143
|
echo ""
|
|
122
144
|
}
|
|
@@ -141,22 +163,38 @@ logs)
|
|
|
141
163
|
restart)
|
|
142
164
|
need_sudo
|
|
143
165
|
banner "Restarting services"
|
|
166
|
+
echo ""
|
|
167
|
+
spin_start "Restarting plc_watcher and plc_web"
|
|
144
168
|
sudo systemctl restart plc_watcher plc_web
|
|
145
169
|
sleep 2
|
|
170
|
+
spin_ok
|
|
171
|
+
echo ""
|
|
146
172
|
systemctl status plc_watcher plc_web --no-pager | grep -E 'Active|Main PID'
|
|
173
|
+
echo ""
|
|
147
174
|
;;
|
|
175
|
+
|
|
148
176
|
start)
|
|
149
177
|
need_sudo
|
|
150
178
|
banner "Starting services"
|
|
179
|
+
echo ""
|
|
180
|
+
spin_start "Starting plc_watcher and plc_web"
|
|
151
181
|
sudo systemctl start plc_watcher plc_web
|
|
152
|
-
|
|
182
|
+
sleep 1
|
|
183
|
+
spin_ok
|
|
184
|
+
echo ""
|
|
153
185
|
;;
|
|
186
|
+
|
|
154
187
|
stop)
|
|
155
188
|
need_sudo
|
|
156
189
|
banner "Stopping services"
|
|
190
|
+
echo ""
|
|
191
|
+
spin_start "Stopping plc_watcher and plc_web"
|
|
157
192
|
sudo systemctl stop plc_watcher plc_web
|
|
158
|
-
|
|
193
|
+
spin_ok
|
|
194
|
+
echo ""
|
|
195
|
+
warn "Services stopped — will restart automatically on next boot"
|
|
159
196
|
warn "To disable auto-start: sudo systemctl disable plc_watcher plc_web"
|
|
197
|
+
echo ""
|
|
160
198
|
;;
|
|
161
199
|
|
|
162
200
|
# ── SMB queue ─────────────────────────────────────────────────────────────────
|
|
@@ -180,7 +218,7 @@ queue)
|
|
|
180
218
|
if [[ -f "$LEDGER" ]]; then
|
|
181
219
|
SENT=$(wc -l < "$LEDGER")
|
|
182
220
|
ok "Delivery ledger: ${SENT} file(s) sent"
|
|
183
|
-
|
|
221
|
+
sed 's/^/ /' "$LEDGER"
|
|
184
222
|
else
|
|
185
223
|
info "No deliveries recorded yet"
|
|
186
224
|
fi
|
|
@@ -198,7 +236,7 @@ push-test)
|
|
|
198
236
|
LATEST=$(ls -t /home/pi/reports/*.pdf 2>/dev/null | head -1 || true)
|
|
199
237
|
fi
|
|
200
238
|
[[ -z "$LATEST" ]] && { err "Could not generate PDF — is PLC connected?"; exit 1; }
|
|
201
|
-
info "File: $(basename $LATEST)"
|
|
239
|
+
info "File: $(basename "$LATEST")"
|
|
202
240
|
echo ""
|
|
203
241
|
"${PYTHON}" -c "
|
|
204
242
|
import sys, os
|
|
@@ -228,9 +266,11 @@ wifi)
|
|
|
228
266
|
scan|"")
|
|
229
267
|
banner "WiFi Networks"
|
|
230
268
|
echo ""
|
|
231
|
-
|
|
269
|
+
|
|
270
|
+
spin_start "Scanning for networks"
|
|
232
271
|
nmcli dev wifi rescan ifname wlan0 2>/dev/null || true
|
|
233
272
|
sleep 2
|
|
273
|
+
spin_ok
|
|
234
274
|
|
|
235
275
|
mapfile -t RAW < <(
|
|
236
276
|
nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE dev wifi list ifname wlan0 2>/dev/null \
|
|
@@ -274,11 +314,9 @@ wifi)
|
|
|
274
314
|
SEL="${SSIDS[$((CHOICE-1))]}"
|
|
275
315
|
echo ""
|
|
276
316
|
read -r -s -p " Password for '${SEL}' (blank if open): " WIFI_PASS </dev/tty
|
|
277
|
-
echo ""
|
|
278
|
-
echo ""
|
|
317
|
+
echo ""; echo ""
|
|
279
318
|
|
|
280
319
|
need_sudo
|
|
281
|
-
# Remove existing connection if any, then connect
|
|
282
320
|
sudo nmcli connection delete "$SEL" 2>/dev/null || true
|
|
283
321
|
if [[ -n "$WIFI_PASS" ]]; then
|
|
284
322
|
sudo nmcli connection add type wifi ifname wlan0 con-name "$SEL" \
|
|
@@ -291,25 +329,21 @@ wifi)
|
|
|
291
329
|
connection.autoconnect-priority 200 2>/dev/null
|
|
292
330
|
fi
|
|
293
331
|
|
|
294
|
-
|
|
332
|
+
spin_start "Connecting to '${SEL}'"
|
|
295
333
|
if sudo nmcli connection up "$SEL" 2>/dev/null; then
|
|
296
|
-
echo ""
|
|
297
334
|
sleep 2
|
|
298
335
|
NEW_IP=$(ip -4 addr show wlan0 2>/dev/null | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "")
|
|
299
|
-
|
|
300
|
-
[[ -n "$NEW_IP" ]] && ok "
|
|
336
|
+
spin_ok "Connected"
|
|
337
|
+
[[ -n "$NEW_IP" ]] && ok "Pi IP: ${NEW_IP} → http://${NEW_IP}:8080"
|
|
301
338
|
|
|
302
|
-
# Prompt to update SMB host IP
|
|
303
339
|
echo ""
|
|
304
340
|
echo -e " ${Y}Network changed — SMB delivery target may need updating.${NC}"
|
|
305
341
|
read -r -p " Update SMB host IP? [Y/n]: " CONFIRM </dev/tty
|
|
306
342
|
CONFIRM="${CONFIRM:-Y}"
|
|
307
|
-
|
|
343
|
+
[[ "${CONFIRM^^}" == "Y" ]] && \
|
|
308
344
|
prompt_smb_config "Network changed to '${SEL}' (Pi IP: ${NEW_IP:-unknown})"
|
|
309
|
-
fi
|
|
310
345
|
else
|
|
311
|
-
|
|
312
|
-
warn "Could not connect — check password and try again"
|
|
346
|
+
spin_warn "Could not connect — check password and try again"
|
|
313
347
|
fi
|
|
314
348
|
;;
|
|
315
349
|
|
|
@@ -334,41 +368,31 @@ hotspot)
|
|
|
334
368
|
banner "Enable Hotspot"
|
|
335
369
|
need_sudo
|
|
336
370
|
echo ""
|
|
337
|
-
|
|
338
|
-
# Allow custom SSID/pass as args
|
|
339
371
|
[[ -n "${1:-}" ]] && HOTSPOT_SSID="$1"
|
|
340
372
|
[[ -n "${2:-}" ]] && HOTSPOT_PASS="$2"
|
|
341
373
|
|
|
342
|
-
# Bring down existing hotspot connection if any
|
|
343
374
|
sudo nmcli connection delete "$HOTSPOT_CON" 2>/dev/null || true
|
|
344
375
|
|
|
345
|
-
|
|
376
|
+
spin_start "Starting hotspot '${HOTSPOT_SSID}'"
|
|
346
377
|
sudo nmcli device wifi hotspot \
|
|
347
|
-
ifname wlan0 \
|
|
348
|
-
|
|
349
|
-
ssid "$HOTSPOT_SSID" \
|
|
350
|
-
password "$HOTSPOT_PASS" 2>&1 | grep -v "^$" || true
|
|
351
|
-
|
|
378
|
+
ifname wlan0 con-name "$HOTSPOT_CON" \
|
|
379
|
+
ssid "$HOTSPOT_SSID" password "$HOTSPOT_PASS" &>/dev/null || true
|
|
352
380
|
sleep 2
|
|
381
|
+
spin_ok "Hotspot active"
|
|
353
382
|
|
|
354
|
-
# Get Pi's hotspot IP (usually 10.42.0.1)
|
|
355
383
|
HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
|
|
356
384
|
| grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "10.42.0.1")
|
|
357
385
|
|
|
358
|
-
echo ""
|
|
359
|
-
ok "Hotspot active"
|
|
360
386
|
echo ""
|
|
361
387
|
echo -e " ${W}┌────────────────────────────────────────────────┐${NC}"
|
|
362
388
|
echo -e " ${W}│ Connect your PC to this WiFi: │${NC}"
|
|
363
389
|
echo -e " ${W}│ │${NC}"
|
|
364
|
-
printf " ${W}│ %-14s ${G}%-30s${W} │${NC}\n" "SSID:"
|
|
390
|
+
printf " ${W}│ %-14s ${G}%-30s${W} │${NC}\n" "SSID:" "$HOTSPOT_SSID"
|
|
365
391
|
printf " ${W}│ %-14s ${G}%-30s${W} │${NC}\n" "Password:" "$HOTSPOT_PASS"
|
|
366
|
-
printf " ${W}│ %-14s ${C}%-30s${W} │${NC}\n" "Pi IP:"
|
|
392
|
+
printf " ${W}│ %-14s ${C}%-30s${W} │${NC}\n" "Pi IP:" "$HOTSPOT_IP"
|
|
367
393
|
echo -e " ${W}└────────────────────────────────────────────────┘${NC}"
|
|
368
394
|
echo ""
|
|
369
395
|
info "After PC connects, run: plc_checkweigher hotspot scan"
|
|
370
|
-
info " — detects PC's IP and updates SMB config automatically"
|
|
371
|
-
echo ""
|
|
372
396
|
info "Web UI still accessible at: http://${HOTSPOT_IP}:8080"
|
|
373
397
|
echo ""
|
|
374
398
|
;;
|
|
@@ -376,9 +400,11 @@ hotspot)
|
|
|
376
400
|
off)
|
|
377
401
|
banner "Disable Hotspot"
|
|
378
402
|
need_sudo
|
|
403
|
+
echo ""
|
|
404
|
+
spin_start "Stopping hotspot"
|
|
379
405
|
sudo nmcli connection delete "$HOTSPOT_CON" 2>/dev/null \
|
|
380
|
-
&&
|
|
381
|
-
||
|
|
406
|
+
&& spin_ok \
|
|
407
|
+
|| spin_warn "No active hotspot found"
|
|
382
408
|
echo ""
|
|
383
409
|
info "To reconnect to WiFi: plc_checkweigher wifi"
|
|
384
410
|
echo ""
|
|
@@ -401,29 +427,34 @@ hotspot)
|
|
|
401
427
|
;;
|
|
402
428
|
|
|
403
429
|
scan)
|
|
404
|
-
# Find devices connected to the hotspot subnet and offer to set as SMB target
|
|
405
430
|
banner "Scanning for connected devices"
|
|
406
431
|
echo ""
|
|
407
432
|
|
|
408
433
|
HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
|
|
409
434
|
| grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "")
|
|
410
|
-
|
|
411
435
|
if [[ -z "$HOTSPOT_IP" ]]; then
|
|
412
436
|
err "Hotspot not active — run: plc_checkweigher hotspot on"
|
|
413
437
|
exit 1
|
|
414
438
|
fi
|
|
415
439
|
|
|
416
440
|
SUBNET="${HOTSPOT_IP%.*}.0/24"
|
|
417
|
-
info "
|
|
441
|
+
info "Sweeping ${SUBNET} ..."
|
|
442
|
+
echo ""
|
|
418
443
|
|
|
419
|
-
#
|
|
444
|
+
# Parallel ping sweep with live counter
|
|
445
|
+
DONE=0
|
|
420
446
|
for i in $(seq 1 254); do
|
|
421
447
|
ping -c 1 -W 1 "${HOTSPOT_IP%.*}.${i}" &>/dev/null &
|
|
448
|
+
DONE=$((DONE + 1))
|
|
449
|
+
if [[ $_TTY -eq 1 && $((DONE % 16)) -eq 0 ]]; then
|
|
450
|
+
printf "\r ${C}⠹${NC} Pinging hosts ... ${D}%d / 254${NC} " "$DONE"
|
|
451
|
+
fi
|
|
422
452
|
done
|
|
423
453
|
wait
|
|
454
|
+
[[ $_TTY -eq 1 ]] && printf '\r\033[K'
|
|
424
455
|
sleep 1
|
|
456
|
+
ok "Subnet sweep complete"
|
|
425
457
|
|
|
426
|
-
# Collect clients (exclude Pi itself)
|
|
427
458
|
mapfile -t CLIENTS < <(
|
|
428
459
|
ip neigh show 2>/dev/null \
|
|
429
460
|
| grep "REACHABLE\|STALE\|DELAY" \
|
|
@@ -434,10 +465,10 @@ hotspot)
|
|
|
434
465
|
)
|
|
435
466
|
|
|
436
467
|
if [[ ${#CLIENTS[@]} -eq 0 ]]; then
|
|
437
|
-
warn "No devices found yet — make sure the PC is connected to '${HOTSPOT_SSID}'"
|
|
438
|
-
info "Try again in a few seconds: plc_checkweigher hotspot scan"
|
|
439
468
|
echo ""
|
|
440
|
-
|
|
469
|
+
warn "No devices found — make sure the PC is connected to '${HOTSPOT_SSID}'"
|
|
470
|
+
info "Try again: plc_checkweigher hotspot scan"
|
|
471
|
+
echo ""; exit 0
|
|
441
472
|
fi
|
|
442
473
|
|
|
443
474
|
echo ""
|
|
@@ -448,7 +479,8 @@ hotspot)
|
|
|
448
479
|
for i in "${!CLIENTS[@]}"; do
|
|
449
480
|
IP="${CLIENTS[$i]}"
|
|
450
481
|
CLIENT_IPS[$i]="$IP"
|
|
451
|
-
HOST=$(host "$IP" 2>/dev/null | grep "domain name pointer"
|
|
482
|
+
HOST=$(host "$IP" 2>/dev/null | grep "domain name pointer" \
|
|
483
|
+
| awk '{print $NF}' | sed 's/\.$//' || echo "")
|
|
452
484
|
printf " %-4s %-18s %s\n" "$((i+1)))" "$IP" "${HOST:-(unknown)}"
|
|
453
485
|
done
|
|
454
486
|
hr
|
|
@@ -489,18 +521,26 @@ display)
|
|
|
489
521
|
on)
|
|
490
522
|
banner "Enable Display"
|
|
491
523
|
need_sudo
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
524
|
+
echo ""
|
|
525
|
+
spin_start "Starting LightDM"
|
|
526
|
+
if sudo systemctl start lightdm 2>/dev/null; then
|
|
527
|
+
spin_ok "Display enabled"
|
|
528
|
+
else
|
|
529
|
+
spin_warn "LightDM failed to start (may not be installed)"
|
|
530
|
+
fi
|
|
495
531
|
sudo systemctl enable lightdm 2>/dev/null && ok "Auto-start enabled" || true
|
|
496
532
|
echo ""
|
|
497
533
|
;;
|
|
498
534
|
off)
|
|
499
535
|
banner "Disable Display"
|
|
500
536
|
need_sudo
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
537
|
+
echo ""
|
|
538
|
+
spin_start "Stopping LightDM"
|
|
539
|
+
if sudo systemctl stop lightdm 2>/dev/null; then
|
|
540
|
+
spin_ok "Display disabled"
|
|
541
|
+
else
|
|
542
|
+
spin_warn "LightDM was not running"
|
|
543
|
+
fi
|
|
504
544
|
read -r -p " Disable auto-start on boot too? [y/N]: " DIS </dev/tty
|
|
505
545
|
DIS="${DIS:-N}"
|
|
506
546
|
if [[ "${DIS^^}" == "Y" ]]; then
|
|
@@ -511,17 +551,15 @@ display)
|
|
|
511
551
|
status)
|
|
512
552
|
banner "Display Status"
|
|
513
553
|
echo ""
|
|
514
|
-
ACTIVE=$(systemctl is-active
|
|
515
|
-
ENABLED=$(systemctl is-enabled lightdm 2>/dev/null ||
|
|
554
|
+
ACTIVE=$(systemctl is-active lightdm 2>/dev/null || true)
|
|
555
|
+
ENABLED=$(systemctl is-enabled lightdm 2>/dev/null || true)
|
|
516
556
|
if [[ "$ACTIVE" == "active" ]]; then
|
|
517
557
|
ok "LightDM: RUNNING (auto-start: ${ENABLED})"
|
|
518
558
|
else
|
|
519
559
|
info "LightDM: ${ACTIVE} (auto-start: ${ENABLED})"
|
|
520
560
|
fi
|
|
521
|
-
# HDMI / display connected
|
|
522
561
|
if command -v tvservice &>/dev/null; then
|
|
523
|
-
HDMI
|
|
524
|
-
info "HDMI: ${HDMI}"
|
|
562
|
+
info "HDMI: $(tvservice -s 2>/dev/null | head -1)"
|
|
525
563
|
fi
|
|
526
564
|
echo ""
|
|
527
565
|
;;
|
|
@@ -538,6 +576,167 @@ smb-config)
|
|
|
538
576
|
prompt_smb_config
|
|
539
577
|
;;
|
|
540
578
|
|
|
579
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
580
|
+
# UNINSTALL — remove everything setup.sh installed
|
|
581
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
582
|
+
uninstall)
|
|
583
|
+
PI_USER="${PI_USER:-pi}"
|
|
584
|
+
HOME_DIR="/home/${PI_USER}"
|
|
585
|
+
VENV_DIR="${HOME_DIR}/plc_env"
|
|
586
|
+
REPORTS_DIR="${HOME_DIR}/reports"
|
|
587
|
+
BOOT_FW="/boot/firmware"
|
|
588
|
+
|
|
589
|
+
echo ""
|
|
590
|
+
echo -e "${R} ╔══════════════════════════════════════════════════════════╗${NC}"
|
|
591
|
+
echo -e "${R} ║ PLC CHECK-WEIGHER UNINSTALLER ║${NC}"
|
|
592
|
+
echo -e "${R} ╚══════════════════════════════════════════════════════════╝${NC}"
|
|
593
|
+
echo ""
|
|
594
|
+
echo -e " This will permanently remove:"
|
|
595
|
+
echo ""
|
|
596
|
+
echo -e " ${R}✗${NC} systemd services plc_watcher plc_web"
|
|
597
|
+
echo -e " ${R}✗${NC} project code ${INSTALL_DIR}"
|
|
598
|
+
echo -e " ${R}✗${NC} Python venv ${VENV_DIR}"
|
|
599
|
+
echo -e " ${R}✗${NC} CLI tool /usr/local/bin/plc_checkweigher"
|
|
600
|
+
echo -e " ${R}✗${NC} Plymouth theme saismruth (reverts to default)"
|
|
601
|
+
echo -e " ${R}✗${NC} RT kernel config (reverts /boot/firmware/config.txt)"
|
|
602
|
+
echo -e " ${R}✗${NC} LightDM drop-in /etc/systemd/system/lightdm.service.d/"
|
|
603
|
+
echo -e " ${R}✗${NC} NetworkManager cfg /etc/systemd/system/NetworkManager-wait-online.service.d/"
|
|
604
|
+
echo -e " ${R}✗${NC} Hotspot connection plc-hotspot (nmcli)"
|
|
605
|
+
echo -e " ${Y}!${NC} reports folder ${REPORTS_DIR} (you will be asked)"
|
|
606
|
+
echo ""
|
|
607
|
+
echo -e " ${D}System packages (git, python3-venv, samba-client) are NOT removed.${NC}"
|
|
608
|
+
echo ""
|
|
609
|
+
hr
|
|
610
|
+
echo ""
|
|
611
|
+
|
|
612
|
+
read -r -p " Type YES to confirm full uninstall: " CONFIRM </dev/tty
|
|
613
|
+
[[ "$CONFIRM" == "YES" ]] || { echo " Aborted."; exit 0; }
|
|
614
|
+
|
|
615
|
+
echo ""
|
|
616
|
+
read -r -p " Keep report PDFs in ${REPORTS_DIR}? [Y/n]: " KEEP_REPORTS </dev/tty
|
|
617
|
+
KEEP_REPORTS="${KEEP_REPORTS:-Y}"
|
|
618
|
+
|
|
619
|
+
echo ""
|
|
620
|
+
need_sudo
|
|
621
|
+
|
|
622
|
+
# Step counter
|
|
623
|
+
_US=0; _UT=9
|
|
624
|
+
ustep() { _US=$((_US + 1)); spin_start "[${_US}/${_UT}] $*"; }
|
|
625
|
+
|
|
626
|
+
# ── 1. Stop and disable services ─────────────────────────────────────────
|
|
627
|
+
echo ""
|
|
628
|
+
ustep "Stopping and disabling services"
|
|
629
|
+
for SVC in plc_watcher plc_web; do
|
|
630
|
+
systemctl is-active --quiet "$SVC" 2>/dev/null \
|
|
631
|
+
&& sudo systemctl stop "$SVC" 2>/dev/null || true
|
|
632
|
+
systemctl is-enabled --quiet "$SVC" 2>/dev/null \
|
|
633
|
+
&& sudo systemctl disable "$SVC" 2>/dev/null || true
|
|
634
|
+
done
|
|
635
|
+
sudo rm -f /etc/systemd/system/plc_watcher.service \
|
|
636
|
+
/etc/systemd/system/plc_web.service
|
|
637
|
+
spin_ok "Services removed"
|
|
638
|
+
|
|
639
|
+
# ── 2. System drop-ins ───────────────────────────────────────────────────
|
|
640
|
+
ustep "Removing system drop-ins"
|
|
641
|
+
sudo rm -f /etc/systemd/system/lightdm.service.d/display-priority.conf
|
|
642
|
+
sudo rm -f /etc/systemd/system/NetworkManager-wait-online.service.d/timeout.conf
|
|
643
|
+
sudo rm -f /etc/tmpfiles.d/utmp-fix.conf
|
|
644
|
+
sudo systemctl reenable lightdm 2>/dev/null || true
|
|
645
|
+
spin_ok
|
|
646
|
+
|
|
647
|
+
# ── 3. Plymouth theme ────────────────────────────────────────────────────
|
|
648
|
+
ustep "Removing Plymouth theme"
|
|
649
|
+
THEME_DIR="/usr/share/plymouth/themes/saismruth"
|
|
650
|
+
[[ -d "$THEME_DIR" ]] && sudo rm -rf "$THEME_DIR"
|
|
651
|
+
DEFAULT_THEME=$(sudo plymouth-set-default-theme --list 2>/dev/null \
|
|
652
|
+
| grep -E "^pix$|^bgrt$|^spinner$" | head -1 || echo "")
|
|
653
|
+
if [[ -n "$DEFAULT_THEME" ]]; then
|
|
654
|
+
sudo plymouth-set-default-theme "$DEFAULT_THEME" 2>/dev/null || true
|
|
655
|
+
else
|
|
656
|
+
sudo plymouth-set-default-theme --reset 2>/dev/null || true
|
|
657
|
+
fi
|
|
658
|
+
spin_ok "Reverted to '${DEFAULT_THEME:-default}'"
|
|
659
|
+
|
|
660
|
+
# ── 4. Rebuild initramfs (slowest step — keep spinner alive) ─────────────
|
|
661
|
+
ustep "Rebuilding initramfs ${D}(~30 s)${NC}"
|
|
662
|
+
sudo update-initramfs -u > /tmp/uninstall_initramfs.log 2>&1 \
|
|
663
|
+
&& spin_ok \
|
|
664
|
+
|| spin_warn "Warnings — see /tmp/uninstall_initramfs.log"
|
|
665
|
+
|
|
666
|
+
# ── 5. RT kernel revert ──────────────────────────────────────────────────
|
|
667
|
+
ustep "Reverting RT kernel config"
|
|
668
|
+
if [[ -f "${BOOT_FW}/config.txt" ]]; then
|
|
669
|
+
sudo sed -i '/### PLC-RT-BLOCK-START ###/,/### PLC-RT-BLOCK-END ###/d' \
|
|
670
|
+
"${BOOT_FW}/config.txt"
|
|
671
|
+
sudo sed -i '/^gpu_mem=128$/d' "${BOOT_FW}/config.txt"
|
|
672
|
+
sudo rm -f "${BOOT_FW}/kernel8-rt.img" \
|
|
673
|
+
"${BOOT_FW}/initramfs8-rt" \
|
|
674
|
+
"${BOOT_FW}/kernel8-stock.img"
|
|
675
|
+
spin_ok "Stock kernel will boot after reboot"
|
|
676
|
+
else
|
|
677
|
+
spin_warn "config.txt not found — skipped"
|
|
678
|
+
fi
|
|
679
|
+
|
|
680
|
+
# ── 6. Hotspot + nmcli cleanup ───────────────────────────────────────────
|
|
681
|
+
ustep "Cleaning up network connections"
|
|
682
|
+
sudo nmcli connection delete "plc-hotspot" 2>/dev/null || true
|
|
683
|
+
sudo systemctl daemon-reload
|
|
684
|
+
spin_ok
|
|
685
|
+
|
|
686
|
+
# ── 7. Python venv ───────────────────────────────────────────────────────
|
|
687
|
+
ustep "Removing Python environment"
|
|
688
|
+
if [[ -d "$VENV_DIR" ]]; then
|
|
689
|
+
rm -rf "$VENV_DIR"
|
|
690
|
+
spin_ok "Removed ${VENV_DIR}"
|
|
691
|
+
else
|
|
692
|
+
spin_ok "Already gone"
|
|
693
|
+
fi
|
|
694
|
+
|
|
695
|
+
# ── 8. Reports (optional) + temp files ───────────────────────────────────
|
|
696
|
+
ustep "Cleaning up runtime files"
|
|
697
|
+
rm -f /tmp/plc_live.json 2>/dev/null || true
|
|
698
|
+
BASHRC="${HOME_DIR}/.bashrc"
|
|
699
|
+
[[ -f "$BASHRC" ]] && sed -i '/export PATH.*\.local\/bin/d' "$BASHRC" 2>/dev/null || true
|
|
700
|
+
sudo rm -f /usr/local/bin/plc_checkweigher
|
|
701
|
+
rm -f "${HOME_DIR}/.local/bin/plc_checkweigher" 2>/dev/null || true
|
|
702
|
+
if [[ "${KEEP_REPORTS^^}" != "Y" && -d "$REPORTS_DIR" ]]; then
|
|
703
|
+
rm -rf "$REPORTS_DIR"
|
|
704
|
+
fi
|
|
705
|
+
spin_ok "CLI and temp files removed"
|
|
706
|
+
|
|
707
|
+
# ── 9. Remove project directory (self-deletes last) ───────────────────────
|
|
708
|
+
ustep "Removing project code"
|
|
709
|
+
if [[ -d "$INSTALL_DIR" ]]; then
|
|
710
|
+
rm -rf "$INSTALL_DIR"
|
|
711
|
+
spin_ok "Removed ${INSTALL_DIR}"
|
|
712
|
+
else
|
|
713
|
+
spin_ok "Already gone"
|
|
714
|
+
fi
|
|
715
|
+
|
|
716
|
+
# ── Done ─────────────────────────────────────────────────────────────────
|
|
717
|
+
echo ""
|
|
718
|
+
echo -e "${G}"
|
|
719
|
+
echo " ╔══════════════════════════════════════════════════════════╗"
|
|
720
|
+
echo " ║ Uninstall complete. ║"
|
|
721
|
+
echo " ║ ║"
|
|
722
|
+
echo " ║ A reboot is needed to apply kernel revert. ║"
|
|
723
|
+
echo " ╚══════════════════════════════════════════════════════════╝"
|
|
724
|
+
echo -e "${NC}"
|
|
725
|
+
[[ "${KEEP_REPORTS^^}" == "Y" ]] && info "PDFs still at: ${REPORTS_DIR} (remove manually if needed)"
|
|
726
|
+
echo ""
|
|
727
|
+
|
|
728
|
+
read -r -p " Reboot now? [Y/n]: " DO_REBOOT </dev/tty
|
|
729
|
+
DO_REBOOT="${DO_REBOOT:-Y}"
|
|
730
|
+
if [[ "${DO_REBOOT^^}" == "Y" ]]; then
|
|
731
|
+
spin_start "Rebooting"
|
|
732
|
+
sleep 1
|
|
733
|
+
sudo reboot
|
|
734
|
+
else
|
|
735
|
+
warn "Remember to reboot for kernel changes to take effect."
|
|
736
|
+
echo ""
|
|
737
|
+
fi
|
|
738
|
+
;;
|
|
739
|
+
|
|
541
740
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
542
741
|
# HELP
|
|
543
742
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -571,6 +770,8 @@ help|--help|-h)
|
|
|
571
770
|
echo " display off Disable display (stop LightDM)"
|
|
572
771
|
echo " display status Show display state"
|
|
573
772
|
echo ""
|
|
773
|
+
echo -e " ${R} uninstall${NC} Remove everything setup.sh installed"
|
|
774
|
+
echo ""
|
|
574
775
|
;;
|
|
575
776
|
|
|
576
777
|
*)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plc-checkweigher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.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"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
10
|
"setup.sh",
|
|
11
|
+
"uninstall.sh",
|
|
11
12
|
"assets/"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|
package/uninstall.sh
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# PLC Check-Weigher — Full Uninstaller
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Run directly: sudo bash uninstall.sh
|
|
6
|
+
# Via npx: npx plc-checkweigher -ex
|
|
7
|
+
# =============================================================================
|
|
8
|
+
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
PI_USER="${PI_USER:-pi}"
|
|
12
|
+
HOME_DIR="/home/${PI_USER}"
|
|
13
|
+
INSTALL_DIR="${HOME_DIR}/plc_checkweigher"
|
|
14
|
+
VENV_DIR="${HOME_DIR}/plc_env"
|
|
15
|
+
REPORTS_DIR="${HOME_DIR}/reports"
|
|
16
|
+
BOOT_FW="/boot/firmware"
|
|
17
|
+
|
|
18
|
+
B='\033[1;34m'; G='\033[0;32m'; R='\033[1;31m'; Y='\033[1;33m'
|
|
19
|
+
C='\033[0;36m'; D='\033[2m'; NC='\033[0m'
|
|
20
|
+
|
|
21
|
+
ok() { echo -e " ${G}✓${NC} $*"; }
|
|
22
|
+
warn() { echo -e " ${Y}!${NC} $*"; }
|
|
23
|
+
info() { echo -e " ${C}i${NC} $*"; }
|
|
24
|
+
hr() { echo -e " ${D}$(printf '─%.0s' {1..56})${NC}"; }
|
|
25
|
+
banner() {
|
|
26
|
+
echo ""
|
|
27
|
+
echo -e "${B} ▸ $*${NC}"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# ── Spinner ───────────────────────────────────────────────────────────────────
|
|
31
|
+
_SP_PID=""; _SP_MSG=""
|
|
32
|
+
_SP_FRAMES=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
|
|
33
|
+
_TTY=0; [[ -t 1 ]] && _TTY=1
|
|
34
|
+
|
|
35
|
+
_spin_kill() {
|
|
36
|
+
if [[ -n "$_SP_PID" ]]; then
|
|
37
|
+
kill "$_SP_PID" 2>/dev/null || true
|
|
38
|
+
wait "$_SP_PID" 2>/dev/null || true
|
|
39
|
+
_SP_PID=""
|
|
40
|
+
fi
|
|
41
|
+
if [[ $_TTY -eq 1 ]]; then printf '\r\033[K'; fi
|
|
42
|
+
return 0
|
|
43
|
+
}
|
|
44
|
+
trap '_spin_kill' EXIT INT TERM
|
|
45
|
+
|
|
46
|
+
spin_start() {
|
|
47
|
+
_SP_MSG="${1:-}"
|
|
48
|
+
[[ $_TTY -eq 0 ]] && { echo -e " ${C}…${NC} ${_SP_MSG}"; return; }
|
|
49
|
+
_spin_kill
|
|
50
|
+
( local i=0
|
|
51
|
+
while true; do
|
|
52
|
+
printf "\r ${C}%s${NC} %s " "${_SP_FRAMES[$((i % 10))]}" "${_SP_MSG}"
|
|
53
|
+
sleep 0.08; i=$((i+1))
|
|
54
|
+
done ) &
|
|
55
|
+
_SP_PID=$!
|
|
56
|
+
}
|
|
57
|
+
spin_ok() { local m="${_SP_MSG}"; _spin_kill; ok "${m}${1:+ ${D}$1${NC}}"; return 0; }
|
|
58
|
+
spin_warn() { local m="${_SP_MSG}"; _spin_kill; warn "${m}${1:+ ${D}$1${NC}}"; return 0; }
|
|
59
|
+
|
|
60
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
[[ "${EUID}" -eq 0 ]] || { echo -e "${R}Run as root:${NC} sudo bash uninstall.sh"; exit 1; }
|
|
62
|
+
|
|
63
|
+
echo ""
|
|
64
|
+
echo -e "${R} ╔══════════════════════════════════════════════════════════╗${NC}"
|
|
65
|
+
echo -e "${R} ║ PLC CHECK-WEIGHER UNINSTALLER ║${NC}"
|
|
66
|
+
echo -e "${R} ╚══════════════════════════════════════════════════════════╝${NC}"
|
|
67
|
+
echo ""
|
|
68
|
+
echo -e " This will permanently remove:"
|
|
69
|
+
echo ""
|
|
70
|
+
echo -e " ${R}✗${NC} systemd services plc_watcher plc_web"
|
|
71
|
+
echo -e " ${R}✗${NC} project code ${INSTALL_DIR}"
|
|
72
|
+
echo -e " ${R}✗${NC} Python venv ${VENV_DIR}"
|
|
73
|
+
echo -e " ${R}✗${NC} CLI tool /usr/local/bin/plc_checkweigher"
|
|
74
|
+
echo -e " ${R}✗${NC} Plymouth theme saismruth (reverts to default)"
|
|
75
|
+
echo -e " ${R}✗${NC} RT kernel config (reverts /boot/firmware/config.txt)"
|
|
76
|
+
echo -e " ${R}✗${NC} LightDM drop-in /etc/systemd/system/lightdm.service.d/"
|
|
77
|
+
echo -e " ${R}✗${NC} NetworkManager cfg /etc/systemd/system/NetworkManager-wait-online.service.d/"
|
|
78
|
+
echo -e " ${R}✗${NC} Hotspot connection plc-hotspot (nmcli)"
|
|
79
|
+
echo -e " ${Y}!${NC} reports folder ${REPORTS_DIR} (you will be asked)"
|
|
80
|
+
echo ""
|
|
81
|
+
echo -e " ${D}System packages (git, python3-venv, samba-client) are NOT removed.${NC}"
|
|
82
|
+
echo ""
|
|
83
|
+
hr
|
|
84
|
+
echo ""
|
|
85
|
+
|
|
86
|
+
read -r -p " Type YES to confirm full uninstall: " CONFIRM </dev/tty
|
|
87
|
+
[[ "$CONFIRM" == "YES" ]] || { echo " Aborted."; exit 0; }
|
|
88
|
+
|
|
89
|
+
echo ""
|
|
90
|
+
read -r -p " Keep report PDFs in ${REPORTS_DIR}? [Y/n]: " KEEP_REPORTS </dev/tty
|
|
91
|
+
KEEP_REPORTS="${KEEP_REPORTS:-Y}"
|
|
92
|
+
echo ""
|
|
93
|
+
|
|
94
|
+
_US=0; _UT=9
|
|
95
|
+
ustep() { _US=$((_US + 1)); spin_start "[${_US}/${_UT}] $*"; }
|
|
96
|
+
|
|
97
|
+
# ── 1. Stop and disable services ─────────────────────────────────────────────
|
|
98
|
+
ustep "Stopping and disabling services"
|
|
99
|
+
for SVC in plc_watcher plc_web; do
|
|
100
|
+
systemctl is-active --quiet "$SVC" 2>/dev/null && systemctl stop "$SVC" 2>/dev/null || true
|
|
101
|
+
systemctl is-enabled --quiet "$SVC" 2>/dev/null && systemctl disable "$SVC" 2>/dev/null || true
|
|
102
|
+
done
|
|
103
|
+
rm -f /etc/systemd/system/plc_watcher.service \
|
|
104
|
+
/etc/systemd/system/plc_web.service
|
|
105
|
+
spin_ok "Services removed"
|
|
106
|
+
|
|
107
|
+
# ── 2. System drop-ins ────────────────────────────────────────────────────────
|
|
108
|
+
ustep "Removing system drop-ins"
|
|
109
|
+
rm -f /etc/systemd/system/lightdm.service.d/display-priority.conf
|
|
110
|
+
rm -f /etc/systemd/system/NetworkManager-wait-online.service.d/timeout.conf
|
|
111
|
+
rm -f /etc/tmpfiles.d/utmp-fix.conf
|
|
112
|
+
systemctl reenable lightdm 2>/dev/null || true
|
|
113
|
+
spin_ok
|
|
114
|
+
|
|
115
|
+
# ── 3. Plymouth theme ────────────────────────────────────────────────────────
|
|
116
|
+
ustep "Removing Plymouth theme"
|
|
117
|
+
THEME_DIR="/usr/share/plymouth/themes/saismruth"
|
|
118
|
+
[[ -d "$THEME_DIR" ]] && rm -rf "$THEME_DIR"
|
|
119
|
+
DEFAULT_THEME=$(plymouth-set-default-theme --list 2>/dev/null \
|
|
120
|
+
| grep -E "^pix$|^bgrt$|^spinner$" | head -1 || echo "")
|
|
121
|
+
if [[ -n "$DEFAULT_THEME" ]]; then
|
|
122
|
+
plymouth-set-default-theme "$DEFAULT_THEME" 2>/dev/null || true
|
|
123
|
+
else
|
|
124
|
+
plymouth-set-default-theme --reset 2>/dev/null || true
|
|
125
|
+
fi
|
|
126
|
+
spin_ok "Reverted to '${DEFAULT_THEME:-default}'"
|
|
127
|
+
|
|
128
|
+
# ── 4. Rebuild initramfs ──────────────────────────────────────────────────────
|
|
129
|
+
ustep "Rebuilding initramfs (${D}~30 s${NC})"
|
|
130
|
+
update-initramfs -u > /tmp/uninstall_initramfs.log 2>&1 \
|
|
131
|
+
&& spin_ok \
|
|
132
|
+
|| spin_warn "Warnings — see /tmp/uninstall_initramfs.log"
|
|
133
|
+
|
|
134
|
+
# ── 5. RT kernel revert ───────────────────────────────────────────────────────
|
|
135
|
+
ustep "Reverting RT kernel config"
|
|
136
|
+
if [[ -f "${BOOT_FW}/config.txt" ]]; then
|
|
137
|
+
sed -i '/### PLC-RT-BLOCK-START ###/,/### PLC-RT-BLOCK-END ###/d' "${BOOT_FW}/config.txt"
|
|
138
|
+
sed -i '/^gpu_mem=128$/d' "${BOOT_FW}/config.txt"
|
|
139
|
+
rm -f "${BOOT_FW}/kernel8-rt.img" \
|
|
140
|
+
"${BOOT_FW}/initramfs8-rt" \
|
|
141
|
+
"${BOOT_FW}/kernel8-stock.img"
|
|
142
|
+
spin_ok "Stock kernel will boot after reboot"
|
|
143
|
+
else
|
|
144
|
+
spin_warn "config.txt not found — skipped"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# ── 6. Network cleanup ────────────────────────────────────────────────────────
|
|
148
|
+
ustep "Cleaning up network connections"
|
|
149
|
+
nmcli connection delete "plc-hotspot" 2>/dev/null || true
|
|
150
|
+
systemctl daemon-reload
|
|
151
|
+
spin_ok
|
|
152
|
+
|
|
153
|
+
# ── 7. Python venv ────────────────────────────────────────────────────────────
|
|
154
|
+
ustep "Removing Python environment"
|
|
155
|
+
if [[ -d "$VENV_DIR" ]]; then
|
|
156
|
+
rm -rf "$VENV_DIR"
|
|
157
|
+
spin_ok "Removed ${VENV_DIR}"
|
|
158
|
+
else
|
|
159
|
+
spin_ok "Already gone"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# ── 8. Runtime files + CLI ────────────────────────────────────────────────────
|
|
163
|
+
ustep "Cleaning up runtime files and CLI"
|
|
164
|
+
rm -f /tmp/plc_live.json 2>/dev/null || true
|
|
165
|
+
BASHRC="${HOME_DIR}/.bashrc"
|
|
166
|
+
[[ -f "$BASHRC" ]] && sed -i '/export PATH.*\.local\/bin/d' "$BASHRC" 2>/dev/null || true
|
|
167
|
+
rm -f /usr/local/bin/plc_checkweigher
|
|
168
|
+
rm -f "${HOME_DIR}/.local/bin/plc_checkweigher" 2>/dev/null || true
|
|
169
|
+
if [[ "${KEEP_REPORTS^^}" != "Y" && -d "$REPORTS_DIR" ]]; then
|
|
170
|
+
rm -rf "$REPORTS_DIR"
|
|
171
|
+
fi
|
|
172
|
+
spin_ok "CLI and temp files removed"
|
|
173
|
+
|
|
174
|
+
# ── 9. Project code ───────────────────────────────────────────────────────────
|
|
175
|
+
ustep "Removing project code"
|
|
176
|
+
if [[ -d "$INSTALL_DIR" ]]; then
|
|
177
|
+
rm -rf "$INSTALL_DIR"
|
|
178
|
+
spin_ok "Removed ${INSTALL_DIR}"
|
|
179
|
+
else
|
|
180
|
+
spin_ok "Already gone"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
184
|
+
echo ""
|
|
185
|
+
echo -e "${G}"
|
|
186
|
+
echo " ╔══════════════════════════════════════════════════════════╗"
|
|
187
|
+
echo " ║ Uninstall complete. ║"
|
|
188
|
+
echo " ║ ║"
|
|
189
|
+
echo " ║ A reboot is needed to apply kernel revert. ║"
|
|
190
|
+
echo " ╚══════════════════════════════════════════════════════════╝"
|
|
191
|
+
echo -e "${NC}"
|
|
192
|
+
[[ "${KEEP_REPORTS^^}" == "Y" ]] && info "PDFs still at: ${REPORTS_DIR} (remove manually if needed)"
|
|
193
|
+
echo ""
|
|
194
|
+
|
|
195
|
+
read -r -p " Reboot now? [Y/n]: " DO_REBOOT </dev/tty
|
|
196
|
+
DO_REBOOT="${DO_REBOOT:-Y}"
|
|
197
|
+
if [[ "${DO_REBOOT^^}" == "Y" ]]; then
|
|
198
|
+
echo ""; info "Rebooting ..."
|
|
199
|
+
reboot
|
|
200
|
+
else
|
|
201
|
+
warn "Remember to reboot for kernel changes to take effect."
|
|
202
|
+
echo ""
|
|
203
|
+
fi
|