plc-checkweigher 1.6.0 → 1.11.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 +165 -41
- package/bin/plc_checkweigher +783 -0
- package/package.json +2 -1
- package/setup.sh +20 -4
- package/uninstall.sh +203 -0
package/bin/cli.js
CHANGED
|
@@ -6,61 +6,185 @@ 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));
|
|
24
47
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
console.log(` ${D}Install:${NC} npx plc-checkweigher ${Y}-tov${NC}`);
|
|
91
|
+
console.log(` ${D}Uninstall:${NC} npx plc-checkweigher ${Y}-ex${NC}`);
|
|
92
|
+
console.log(` ${D}Help:${NC} npx plc-checkweigher ${Y}--help${NC}`);
|
|
93
|
+
console.log('');
|
|
94
|
+
|
|
95
|
+
process.exit(1);
|
|
28
96
|
}
|
|
29
97
|
|
|
30
|
-
// ──
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
98
|
+
// ── Argument parsing ──────────────────────────────────────────────────────────
|
|
99
|
+
const arg = (process.argv[2] || '').trim();
|
|
100
|
+
|
|
101
|
+
const INSTALL_FLAGS = ['-tov', '--install', 'install'];
|
|
102
|
+
const UNINSTALL_FLAGS = ['-ex', '--uninstall', 'uninstall', 'remove'];
|
|
103
|
+
const HELP_FLAGS = ['-h', '--help', 'help'];
|
|
104
|
+
|
|
105
|
+
if (HELP_FLAGS.includes(arg)) {
|
|
106
|
+
console.log(`
|
|
107
|
+
${B}npx plc-checkweigher${NC} — PLC Check-Weigher setup utility
|
|
108
|
+
|
|
109
|
+
${Y}npx plc-checkweigher -tov${NC} Install ${D}(set up stack, services, RT kernel)${NC}
|
|
110
|
+
${Y}npx plc-checkweigher -ex${NC} Uninstall ${D}(remove everything)${NC}
|
|
111
|
+
${Y}npx plc-checkweigher --help${NC} Help
|
|
112
|
+
|
|
113
|
+
${D}Aliases:${NC}
|
|
114
|
+
${D}-tov / --install / install${NC}
|
|
115
|
+
${D}-ex / --uninstall / uninstall / remove${NC}
|
|
35
116
|
`);
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let mode = 'access'; // default: show brand banner + access denied
|
|
121
|
+
if (INSTALL_FLAGS.includes(arg)) mode = 'install';
|
|
122
|
+
else if (UNINSTALL_FLAGS.includes(arg)) mode = 'uninstall';
|
|
123
|
+
else if (arg !== '') die(`Unknown argument: ${arg}\nRun: npx plc-checkweigher --help`);
|
|
124
|
+
|
|
125
|
+
// ── Platform guards (skip for help / access-denied) ───────────────────────────
|
|
126
|
+
if (mode !== 'access') {
|
|
127
|
+
if (platform() !== 'linux')
|
|
128
|
+
die('This installer only runs on Raspberry Pi (Linux). Got: ' + platform());
|
|
129
|
+
if (arch() !== 'arm64')
|
|
130
|
+
die('Requires 64-bit ARM (arm64). Got: ' + arch() +
|
|
131
|
+
'\nMake sure you are running 64-bit Raspberry Pi OS.');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Locate scripts ────────────────────────────────────────────────────────────
|
|
135
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
136
|
+
const setupScript = path.join(pkgRoot, 'setup.sh');
|
|
137
|
+
const uninstallScript = path.join(pkgRoot, 'uninstall.sh');
|
|
138
|
+
|
|
139
|
+
// ── Dispatch ──────────────────────────────────────────────────────────────────
|
|
140
|
+
if (mode === 'access') {
|
|
141
|
+
showAccessDenied(); // exits with code 1
|
|
142
|
+
}
|
|
36
143
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
144
|
+
if (mode === 'install') {
|
|
145
|
+
if (!fs.existsSync(setupScript))
|
|
146
|
+
die('setup.sh not found — try: npx plc-checkweigher@latest -tov');
|
|
147
|
+
|
|
148
|
+
console.log(`
|
|
149
|
+
${B}╔══════════════════════════════════════════════╗
|
|
150
|
+
║ PLC Check-Weigher — Full Stack Installer ║
|
|
151
|
+
╚══════════════════════════════════════════════╝${NC}
|
|
152
|
+
`);
|
|
153
|
+
console.log(`${Y}This will:${NC}`);
|
|
154
|
+
console.log(' 1. Install the PREEMPT_RT real-time kernel (reboots once)');
|
|
155
|
+
console.log(' 2. Install all Python dependencies');
|
|
156
|
+
console.log(' 3. Clone / update the plc-checkweigher repo');
|
|
157
|
+
console.log(' 4. Configure WiFi, SMB file sharing (credentials → smb_config.py)');
|
|
158
|
+
console.log(' 5. Install systemd services with RT scheduling priority');
|
|
159
|
+
console.log(' 6. Set up live dashboard → http://<pi-ip>:8080/live');
|
|
160
|
+
console.log(' 7. Set up PDF report viewer with instant auto-refresh');
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log(`${Y}Sudo password required to make system-level changes.${NC}`);
|
|
163
|
+
console.log('');
|
|
164
|
+
|
|
165
|
+
const result = spawnSync('sudo', ['bash', setupScript], {
|
|
166
|
+
stdio: 'inherit',
|
|
167
|
+
env: process.env,
|
|
168
|
+
});
|
|
169
|
+
process.exit(result.status ?? 0);
|
|
41
170
|
}
|
|
42
171
|
|
|
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);
|
|
172
|
+
if (mode === 'uninstall') {
|
|
173
|
+
if (!fs.existsSync(uninstallScript))
|
|
174
|
+
die('uninstall.sh not found — try: npx plc-checkweigher@latest -ex');
|
|
175
|
+
|
|
176
|
+
console.log(`
|
|
177
|
+
${R}╔══════════════════════════════════════════════╗
|
|
178
|
+
║ PLC Check-Weigher — Uninstaller ║
|
|
179
|
+
╚══════════════════════════════════════════════╝${NC}
|
|
180
|
+
`);
|
|
181
|
+
console.log(`${D}Removes all services, code, venv, kernel config, and CLI tools.${NC}`);
|
|
182
|
+
console.log(`${D}You will be asked whether to keep your PDF reports.${NC}`);
|
|
183
|
+
console.log('');
|
|
184
|
+
|
|
185
|
+
const result = spawnSync('sudo', ['bash', uninstallScript], {
|
|
186
|
+
stdio: 'inherit',
|
|
187
|
+
env: process.env,
|
|
188
|
+
});
|
|
189
|
+
process.exit(result.status ?? 0);
|
|
190
|
+
}
|
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# plc_checkweigher — CLI for the PLC Check-Weigher system
|
|
3
|
+
# Installed to /usr/local/bin/ by setup.sh
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
INSTALL_DIR="/home/pi/plc_checkweigher"
|
|
8
|
+
PYTHON="/home/pi/plc_env/bin/python3"
|
|
9
|
+
SMB_CFG="${INSTALL_DIR}/smb_config.py"
|
|
10
|
+
|
|
11
|
+
# ── TTY detection — animations only when connected to a real terminal ─────────
|
|
12
|
+
[[ -t 1 ]] && _TTY=1 || _TTY=0
|
|
13
|
+
|
|
14
|
+
# ── Colours ───────────────────────────────────────────────────────────────────
|
|
15
|
+
B='\033[1;34m'; G='\033[0;32m'; R='\033[1;31m'; Y='\033[1;33m'
|
|
16
|
+
C='\033[0;36m'; D='\033[2m'; W='\033[1m'; NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
ok() { echo -e " ${G}✓${NC} $*"; }
|
|
19
|
+
err() { echo -e " ${R}✗${NC} $*" >&2; }
|
|
20
|
+
warn() { echo -e " ${Y}!${NC} $*"; }
|
|
21
|
+
info() { echo -e " ${C}i${NC} $*"; }
|
|
22
|
+
hr() { echo -e " ${D}$(printf '─%.0s' {1..56})${NC}"; }
|
|
23
|
+
|
|
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}"
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
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=$!
|
|
69
|
+
}
|
|
70
|
+
|
|
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
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
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) ────
|
|
89
|
+
prompt_smb_config() {
|
|
90
|
+
local context="${1:-}"
|
|
91
|
+
echo ""
|
|
92
|
+
banner "Update SMB Delivery Config"
|
|
93
|
+
[[ -n "$context" ]] && info "$context"
|
|
94
|
+
echo ""
|
|
95
|
+
|
|
96
|
+
local CUR_HOST CUR_SHARE CUR_USER CUR_PASS
|
|
97
|
+
CUR_HOST=$(smb_get "SMB_HOST")
|
|
98
|
+
CUR_SHARE=$(smb_get "SMB_SHARE")
|
|
99
|
+
CUR_USER=$(smb_get "SMB_USERNAME")
|
|
100
|
+
CUR_PASS=$(smb_get "SMB_PASSWORD")
|
|
101
|
+
|
|
102
|
+
hr
|
|
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)}"
|
|
107
|
+
hr
|
|
108
|
+
echo ""
|
|
109
|
+
|
|
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}"
|
|
115
|
+
|
|
116
|
+
cat > "${SMB_CFG}" << EOF
|
|
117
|
+
SMB_ENABLED = True
|
|
118
|
+
SMB_HOST = "${NEW_HOST}"
|
|
119
|
+
SMB_SHARE = "${NEW_SHARE}"
|
|
120
|
+
SMB_USERNAME = "${NEW_USER}"
|
|
121
|
+
SMB_PASSWORD = "${NEW_PASS}"
|
|
122
|
+
SMB_SUBDIR = ""
|
|
123
|
+
EOF
|
|
124
|
+
|
|
125
|
+
echo ""
|
|
126
|
+
ok "smb_config.py updated"
|
|
127
|
+
info "Target: //${NEW_HOST}/${NEW_SHARE} as ${NEW_USER}"
|
|
128
|
+
echo ""
|
|
129
|
+
|
|
130
|
+
spin_start "Pinging ${NEW_HOST}"
|
|
131
|
+
if ping -c 2 -W 2 "${NEW_HOST}" &>/dev/null; then
|
|
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"
|
|
137
|
+
else
|
|
138
|
+
spin_warn "Auth failed — verify share name and credentials on the PC"
|
|
139
|
+
fi
|
|
140
|
+
else
|
|
141
|
+
spin_warn "${NEW_HOST} not reachable — config saved, will retry at runtime"
|
|
142
|
+
fi
|
|
143
|
+
echo ""
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
CMD="${1:-help}"
|
|
148
|
+
shift || true
|
|
149
|
+
|
|
150
|
+
case "$CMD" in
|
|
151
|
+
|
|
152
|
+
# ── System diagnostic ─────────────────────────────────────────────────────────
|
|
153
|
+
status|check|diag)
|
|
154
|
+
exec "${PYTHON}" "${INSTALL_DIR}/debugger.py" "$@"
|
|
155
|
+
;;
|
|
156
|
+
|
|
157
|
+
# ── Live logs ─────────────────────────────────────────────────────────────────
|
|
158
|
+
logs)
|
|
159
|
+
exec journalctl -u plc_watcher -u plc_web -f --no-pager
|
|
160
|
+
;;
|
|
161
|
+
|
|
162
|
+
# ── Service control ───────────────────────────────────────────────────────────
|
|
163
|
+
restart)
|
|
164
|
+
need_sudo
|
|
165
|
+
banner "Restarting services"
|
|
166
|
+
echo ""
|
|
167
|
+
spin_start "Restarting plc_watcher and plc_web"
|
|
168
|
+
sudo systemctl restart plc_watcher plc_web
|
|
169
|
+
sleep 2
|
|
170
|
+
spin_ok
|
|
171
|
+
echo ""
|
|
172
|
+
systemctl status plc_watcher plc_web --no-pager | grep -E 'Active|Main PID'
|
|
173
|
+
echo ""
|
|
174
|
+
;;
|
|
175
|
+
|
|
176
|
+
start)
|
|
177
|
+
need_sudo
|
|
178
|
+
banner "Starting services"
|
|
179
|
+
echo ""
|
|
180
|
+
spin_start "Starting plc_watcher and plc_web"
|
|
181
|
+
sudo systemctl start plc_watcher plc_web
|
|
182
|
+
sleep 1
|
|
183
|
+
spin_ok
|
|
184
|
+
echo ""
|
|
185
|
+
;;
|
|
186
|
+
|
|
187
|
+
stop)
|
|
188
|
+
need_sudo
|
|
189
|
+
banner "Stopping services"
|
|
190
|
+
echo ""
|
|
191
|
+
spin_start "Stopping plc_watcher and plc_web"
|
|
192
|
+
sudo systemctl stop plc_watcher plc_web
|
|
193
|
+
spin_ok
|
|
194
|
+
echo ""
|
|
195
|
+
warn "Services stopped — will restart automatically on next boot"
|
|
196
|
+
warn "To disable auto-start: sudo systemctl disable plc_watcher plc_web"
|
|
197
|
+
echo ""
|
|
198
|
+
;;
|
|
199
|
+
|
|
200
|
+
# ── SMB queue ─────────────────────────────────────────────────────────────────
|
|
201
|
+
queue)
|
|
202
|
+
banner "SMB Delivery Queue"
|
|
203
|
+
echo ""
|
|
204
|
+
QUEUE="${INSTALL_DIR}/delivery_queue.json"
|
|
205
|
+
LEDGER="${INSTALL_DIR}/delivery_sent.log"
|
|
206
|
+
if [[ -f "$QUEUE" ]]; then
|
|
207
|
+
COUNT=$(python3 -c "import json; d=json.load(open('$QUEUE')); print(len(d))" 2>/dev/null || echo 0)
|
|
208
|
+
if [[ "$COUNT" -eq 0 ]]; then
|
|
209
|
+
ok "Queue empty — all reports delivered"
|
|
210
|
+
else
|
|
211
|
+
warn "${COUNT} file(s) pending delivery:"
|
|
212
|
+
python3 -m json.tool "$QUEUE" 2>/dev/null
|
|
213
|
+
fi
|
|
214
|
+
else
|
|
215
|
+
ok "Queue empty"
|
|
216
|
+
fi
|
|
217
|
+
echo ""
|
|
218
|
+
if [[ -f "$LEDGER" ]]; then
|
|
219
|
+
SENT=$(wc -l < "$LEDGER")
|
|
220
|
+
ok "Delivery ledger: ${SENT} file(s) sent"
|
|
221
|
+
sed 's/^/ /' "$LEDGER"
|
|
222
|
+
else
|
|
223
|
+
info "No deliveries recorded yet"
|
|
224
|
+
fi
|
|
225
|
+
echo ""
|
|
226
|
+
;;
|
|
227
|
+
|
|
228
|
+
# ── Push test ─────────────────────────────────────────────────────────────────
|
|
229
|
+
push-test)
|
|
230
|
+
banner "SMB Push Test"
|
|
231
|
+
echo ""
|
|
232
|
+
LATEST=$(ls -t /home/pi/reports/*.pdf 2>/dev/null | head -1 || true)
|
|
233
|
+
if [[ -z "$LATEST" ]]; then
|
|
234
|
+
info "No PDFs found — generating one from PLC..."
|
|
235
|
+
"${PYTHON}" "${INSTALL_DIR}/plc_report.py"
|
|
236
|
+
LATEST=$(ls -t /home/pi/reports/*.pdf 2>/dev/null | head -1 || true)
|
|
237
|
+
fi
|
|
238
|
+
[[ -z "$LATEST" ]] && { err "Could not generate PDF — is PLC connected?"; exit 1; }
|
|
239
|
+
info "File: $(basename "$LATEST")"
|
|
240
|
+
echo ""
|
|
241
|
+
"${PYTHON}" -c "
|
|
242
|
+
import sys, os
|
|
243
|
+
sys.path.insert(0, '${INSTALL_DIR}')
|
|
244
|
+
from pdf_push import _push_smb, _already_sent, _record_sent
|
|
245
|
+
fname = os.path.basename('$LATEST')
|
|
246
|
+
if _already_sent(fname):
|
|
247
|
+
print(f' Already delivered: {fname}')
|
|
248
|
+
print(' Delete the ledger entry to re-test:')
|
|
249
|
+
print(f' sed -i \"/{fname}/d\" ${INSTALL_DIR}/delivery_sent.log')
|
|
250
|
+
sys.exit(0)
|
|
251
|
+
ok = _push_smb('$LATEST')
|
|
252
|
+
if ok:
|
|
253
|
+
_record_sent(fname)
|
|
254
|
+
sys.exit(0 if ok else 1)
|
|
255
|
+
"
|
|
256
|
+
;;
|
|
257
|
+
|
|
258
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
259
|
+
# WIFI — scan + switch network
|
|
260
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
261
|
+
wifi)
|
|
262
|
+
SUBCMD="${1:-scan}"
|
|
263
|
+
shift || true
|
|
264
|
+
|
|
265
|
+
case "$SUBCMD" in
|
|
266
|
+
scan|"")
|
|
267
|
+
banner "WiFi Networks"
|
|
268
|
+
echo ""
|
|
269
|
+
|
|
270
|
+
spin_start "Scanning for networks"
|
|
271
|
+
nmcli dev wifi rescan ifname wlan0 2>/dev/null || true
|
|
272
|
+
sleep 2
|
|
273
|
+
spin_ok
|
|
274
|
+
|
|
275
|
+
mapfile -t RAW < <(
|
|
276
|
+
nmcli -t -f SSID,SIGNAL,SECURITY,IN-USE dev wifi list ifname wlan0 2>/dev/null \
|
|
277
|
+
| awk -F: '$1!=""' \
|
|
278
|
+
| sort -t: -k2 -rn \
|
|
279
|
+
| awk -F: '!seen[$1]++'
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
[[ ${#RAW[@]} -eq 0 ]] && { warn "No networks found."; exit 0; }
|
|
283
|
+
|
|
284
|
+
echo ""
|
|
285
|
+
hr
|
|
286
|
+
printf " ${B}%-4s %-28s %-8s %-10s %s${NC}\n" "#" "SSID" "Signal" "Security" ""
|
|
287
|
+
hr
|
|
288
|
+
declare -a SSIDS
|
|
289
|
+
for i in "${!RAW[@]}"; do
|
|
290
|
+
IFS=':' read -r SSID SIGNAL SECURITY INUSE <<< "${RAW[$i]}"
|
|
291
|
+
SSIDS[$i]="$SSID"
|
|
292
|
+
SIG="${SIGNAL:-0}"
|
|
293
|
+
if [[ $SIG -ge 80 ]]; then BAR="${G}▂▄▆█${NC}"
|
|
294
|
+
elif [[ $SIG -ge 60 ]]; then BAR="${G}▂▄▆ ${NC}"
|
|
295
|
+
elif [[ $SIG -ge 40 ]]; then BAR="${Y}▂▄ ${NC}"
|
|
296
|
+
else BAR="${R}▂ ${NC}"; fi
|
|
297
|
+
ACTIVE=""
|
|
298
|
+
[[ "$INUSE" == "*" ]] && ACTIVE="${G} ← connected${NC}"
|
|
299
|
+
printf " %-4s %-28s %b %3s%% %-10s%b\n" \
|
|
300
|
+
"$((i+1)))" "$SSID" "$BAR" "$SIG" "${SECURITY:---}" "$ACTIVE"
|
|
301
|
+
done
|
|
302
|
+
hr
|
|
303
|
+
printf " %-4s %s\n" "0)" "Cancel"
|
|
304
|
+
echo ""
|
|
305
|
+
|
|
306
|
+
while true; do
|
|
307
|
+
read -r -p " Choose network [1-${#RAW[@]}] or 0 to cancel: " CHOICE </dev/tty
|
|
308
|
+
[[ "$CHOICE" =~ ^[0-9]+$ ]] && \
|
|
309
|
+
[[ "$CHOICE" -ge 0 && "$CHOICE" -le "${#RAW[@]}" ]] && break
|
|
310
|
+
echo -e " ${R}Enter a number 0–${#RAW[@]}${NC}"
|
|
311
|
+
done
|
|
312
|
+
[[ "$CHOICE" -eq 0 ]] && exit 0
|
|
313
|
+
|
|
314
|
+
SEL="${SSIDS[$((CHOICE-1))]}"
|
|
315
|
+
echo ""
|
|
316
|
+
read -r -s -p " Password for '${SEL}' (blank if open): " WIFI_PASS </dev/tty
|
|
317
|
+
echo ""; echo ""
|
|
318
|
+
|
|
319
|
+
need_sudo
|
|
320
|
+
sudo nmcli connection delete "$SEL" 2>/dev/null || true
|
|
321
|
+
if [[ -n "$WIFI_PASS" ]]; then
|
|
322
|
+
sudo nmcli connection add type wifi ifname wlan0 con-name "$SEL" \
|
|
323
|
+
ssid "$SEL" wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$WIFI_PASS" \
|
|
324
|
+
connection.autoconnect yes connection.autoconnect-priority 200 \
|
|
325
|
+
2>/dev/null
|
|
326
|
+
else
|
|
327
|
+
sudo nmcli connection add type wifi ifname wlan0 con-name "$SEL" \
|
|
328
|
+
ssid "$SEL" connection.autoconnect yes \
|
|
329
|
+
connection.autoconnect-priority 200 2>/dev/null
|
|
330
|
+
fi
|
|
331
|
+
|
|
332
|
+
spin_start "Connecting to '${SEL}'"
|
|
333
|
+
if sudo nmcli connection up "$SEL" 2>/dev/null; then
|
|
334
|
+
sleep 2
|
|
335
|
+
NEW_IP=$(ip -4 addr show wlan0 2>/dev/null | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "")
|
|
336
|
+
spin_ok "Connected"
|
|
337
|
+
[[ -n "$NEW_IP" ]] && ok "Pi IP: ${NEW_IP} → http://${NEW_IP}:8080"
|
|
338
|
+
|
|
339
|
+
echo ""
|
|
340
|
+
echo -e " ${Y}Network changed — SMB delivery target may need updating.${NC}"
|
|
341
|
+
read -r -p " Update SMB host IP? [Y/n]: " CONFIRM </dev/tty
|
|
342
|
+
CONFIRM="${CONFIRM:-Y}"
|
|
343
|
+
[[ "${CONFIRM^^}" == "Y" ]] && \
|
|
344
|
+
prompt_smb_config "Network changed to '${SEL}' (Pi IP: ${NEW_IP:-unknown})"
|
|
345
|
+
else
|
|
346
|
+
spin_warn "Could not connect — check password and try again"
|
|
347
|
+
fi
|
|
348
|
+
;;
|
|
349
|
+
|
|
350
|
+
*)
|
|
351
|
+
echo "Usage: plc_checkweigher wifi [scan]"
|
|
352
|
+
;;
|
|
353
|
+
esac
|
|
354
|
+
;;
|
|
355
|
+
|
|
356
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
357
|
+
# HOTSPOT — AP mode for direct PC connection + SMB push
|
|
358
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
359
|
+
hotspot)
|
|
360
|
+
SUBCMD="${1:-status}"
|
|
361
|
+
shift || true
|
|
362
|
+
HOTSPOT_SSID="${HOTSPOT_SSID:-PLC-Reports}"
|
|
363
|
+
HOTSPOT_PASS="${HOTSPOT_PASS:-plcreport}"
|
|
364
|
+
HOTSPOT_CON="plc-hotspot"
|
|
365
|
+
|
|
366
|
+
case "$SUBCMD" in
|
|
367
|
+
on)
|
|
368
|
+
banner "Enable Hotspot"
|
|
369
|
+
need_sudo
|
|
370
|
+
echo ""
|
|
371
|
+
[[ -n "${1:-}" ]] && HOTSPOT_SSID="$1"
|
|
372
|
+
[[ -n "${2:-}" ]] && HOTSPOT_PASS="$2"
|
|
373
|
+
|
|
374
|
+
sudo nmcli connection delete "$HOTSPOT_CON" 2>/dev/null || true
|
|
375
|
+
|
|
376
|
+
spin_start "Starting hotspot '${HOTSPOT_SSID}'"
|
|
377
|
+
sudo nmcli device wifi hotspot \
|
|
378
|
+
ifname wlan0 con-name "$HOTSPOT_CON" \
|
|
379
|
+
ssid "$HOTSPOT_SSID" password "$HOTSPOT_PASS" &>/dev/null || true
|
|
380
|
+
sleep 2
|
|
381
|
+
spin_ok "Hotspot active"
|
|
382
|
+
|
|
383
|
+
HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
|
|
384
|
+
| grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "10.42.0.1")
|
|
385
|
+
|
|
386
|
+
echo ""
|
|
387
|
+
echo -e " ${W}┌────────────────────────────────────────────────┐${NC}"
|
|
388
|
+
echo -e " ${W}│ Connect your PC to this WiFi: │${NC}"
|
|
389
|
+
echo -e " ${W}│ │${NC}"
|
|
390
|
+
printf " ${W}│ %-14s ${G}%-30s${W} │${NC}\n" "SSID:" "$HOTSPOT_SSID"
|
|
391
|
+
printf " ${W}│ %-14s ${G}%-30s${W} │${NC}\n" "Password:" "$HOTSPOT_PASS"
|
|
392
|
+
printf " ${W}│ %-14s ${C}%-30s${W} │${NC}\n" "Pi IP:" "$HOTSPOT_IP"
|
|
393
|
+
echo -e " ${W}└────────────────────────────────────────────────┘${NC}"
|
|
394
|
+
echo ""
|
|
395
|
+
info "After PC connects, run: plc_checkweigher hotspot scan"
|
|
396
|
+
info "Web UI still accessible at: http://${HOTSPOT_IP}:8080"
|
|
397
|
+
echo ""
|
|
398
|
+
;;
|
|
399
|
+
|
|
400
|
+
off)
|
|
401
|
+
banner "Disable Hotspot"
|
|
402
|
+
need_sudo
|
|
403
|
+
echo ""
|
|
404
|
+
spin_start "Stopping hotspot"
|
|
405
|
+
sudo nmcli connection delete "$HOTSPOT_CON" 2>/dev/null \
|
|
406
|
+
&& spin_ok \
|
|
407
|
+
|| spin_warn "No active hotspot found"
|
|
408
|
+
echo ""
|
|
409
|
+
info "To reconnect to WiFi: plc_checkweigher wifi"
|
|
410
|
+
echo ""
|
|
411
|
+
;;
|
|
412
|
+
|
|
413
|
+
status)
|
|
414
|
+
banner "Hotspot Status"
|
|
415
|
+
echo ""
|
|
416
|
+
if nmcli connection show --active 2>/dev/null | grep -q "$HOTSPOT_CON"; then
|
|
417
|
+
HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
|
|
418
|
+
| grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "?")
|
|
419
|
+
ok "Hotspot is ON"
|
|
420
|
+
info "SSID: ${HOTSPOT_SSID} | Pi IP: ${HOTSPOT_IP}"
|
|
421
|
+
info "Run: plc_checkweigher hotspot scan — to find connected PCs"
|
|
422
|
+
else
|
|
423
|
+
info "Hotspot is OFF"
|
|
424
|
+
info "Run: plc_checkweigher hotspot on — to start"
|
|
425
|
+
fi
|
|
426
|
+
echo ""
|
|
427
|
+
;;
|
|
428
|
+
|
|
429
|
+
scan)
|
|
430
|
+
banner "Scanning for connected devices"
|
|
431
|
+
echo ""
|
|
432
|
+
|
|
433
|
+
HOTSPOT_IP=$(ip -4 addr show wlan0 2>/dev/null \
|
|
434
|
+
| grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' || echo "")
|
|
435
|
+
if [[ -z "$HOTSPOT_IP" ]]; then
|
|
436
|
+
err "Hotspot not active — run: plc_checkweigher hotspot on"
|
|
437
|
+
exit 1
|
|
438
|
+
fi
|
|
439
|
+
|
|
440
|
+
SUBNET="${HOTSPOT_IP%.*}.0/24"
|
|
441
|
+
info "Sweeping ${SUBNET} ..."
|
|
442
|
+
echo ""
|
|
443
|
+
|
|
444
|
+
# Parallel ping sweep with live counter
|
|
445
|
+
DONE=0
|
|
446
|
+
for i in $(seq 1 254); do
|
|
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
|
|
452
|
+
done
|
|
453
|
+
wait
|
|
454
|
+
[[ $_TTY -eq 1 ]] && printf '\r\033[K'
|
|
455
|
+
sleep 1
|
|
456
|
+
ok "Subnet sweep complete"
|
|
457
|
+
|
|
458
|
+
mapfile -t CLIENTS < <(
|
|
459
|
+
ip neigh show 2>/dev/null \
|
|
460
|
+
| grep "REACHABLE\|STALE\|DELAY" \
|
|
461
|
+
| grep "${HOTSPOT_IP%.*}\." \
|
|
462
|
+
| grep -v "^${HOTSPOT_IP}" \
|
|
463
|
+
| awk '{print $1}' \
|
|
464
|
+
| sort -t. -k4 -n
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
if [[ ${#CLIENTS[@]} -eq 0 ]]; then
|
|
468
|
+
echo ""
|
|
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
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
echo ""
|
|
475
|
+
hr
|
|
476
|
+
printf " ${B}%-4s %-18s %s${NC}\n" "#" "IP Address" "Hostname"
|
|
477
|
+
hr
|
|
478
|
+
declare -a CLIENT_IPS
|
|
479
|
+
for i in "${!CLIENTS[@]}"; do
|
|
480
|
+
IP="${CLIENTS[$i]}"
|
|
481
|
+
CLIENT_IPS[$i]="$IP"
|
|
482
|
+
HOST=$(host "$IP" 2>/dev/null | grep "domain name pointer" \
|
|
483
|
+
| awk '{print $NF}' | sed 's/\.$//' || echo "")
|
|
484
|
+
printf " %-4s %-18s %s\n" "$((i+1)))" "$IP" "${HOST:-(unknown)}"
|
|
485
|
+
done
|
|
486
|
+
hr
|
|
487
|
+
printf " %-4s %s\n" "0)" "Cancel"
|
|
488
|
+
echo ""
|
|
489
|
+
|
|
490
|
+
while true; do
|
|
491
|
+
read -r -p " Set device as SMB target [1-${#CLIENTS[@]}] or 0 to cancel: " CHOICE </dev/tty
|
|
492
|
+
[[ "$CHOICE" =~ ^[0-9]+$ ]] && \
|
|
493
|
+
[[ "$CHOICE" -ge 0 && "$CHOICE" -le "${#CLIENTS[@]}" ]] && break
|
|
494
|
+
done
|
|
495
|
+
[[ "$CHOICE" -eq 0 ]] && exit 0
|
|
496
|
+
|
|
497
|
+
TARGET_IP="${CLIENT_IPS[$((CHOICE-1))]}"
|
|
498
|
+
echo ""
|
|
499
|
+
info "Selected: ${TARGET_IP}"
|
|
500
|
+
prompt_smb_config "Setting SMB target to hotspot client ${TARGET_IP}"
|
|
501
|
+
;;
|
|
502
|
+
|
|
503
|
+
*)
|
|
504
|
+
echo "Usage: plc_checkweigher hotspot [on|off|status|scan]"
|
|
505
|
+
echo " on [SSID] [PASSWORD] — start hotspot (default: PLC-Reports / plcreport)"
|
|
506
|
+
echo " off — stop hotspot"
|
|
507
|
+
echo " status — show hotspot state"
|
|
508
|
+
echo " scan — find connected PCs and set as SMB target"
|
|
509
|
+
;;
|
|
510
|
+
esac
|
|
511
|
+
;;
|
|
512
|
+
|
|
513
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
514
|
+
# DISPLAY — enable / disable LightDM
|
|
515
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
516
|
+
display)
|
|
517
|
+
SUBCMD="${1:-status}"
|
|
518
|
+
shift || true
|
|
519
|
+
|
|
520
|
+
case "$SUBCMD" in
|
|
521
|
+
on)
|
|
522
|
+
banner "Enable Display"
|
|
523
|
+
need_sudo
|
|
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
|
|
531
|
+
sudo systemctl enable lightdm 2>/dev/null && ok "Auto-start enabled" || true
|
|
532
|
+
echo ""
|
|
533
|
+
;;
|
|
534
|
+
off)
|
|
535
|
+
banner "Disable Display"
|
|
536
|
+
need_sudo
|
|
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
|
|
544
|
+
read -r -p " Disable auto-start on boot too? [y/N]: " DIS </dev/tty
|
|
545
|
+
DIS="${DIS:-N}"
|
|
546
|
+
if [[ "${DIS^^}" == "Y" ]]; then
|
|
547
|
+
sudo systemctl disable lightdm 2>/dev/null && ok "Auto-start disabled" || true
|
|
548
|
+
fi
|
|
549
|
+
echo ""
|
|
550
|
+
;;
|
|
551
|
+
status)
|
|
552
|
+
banner "Display Status"
|
|
553
|
+
echo ""
|
|
554
|
+
ACTIVE=$(systemctl is-active lightdm 2>/dev/null || true)
|
|
555
|
+
ENABLED=$(systemctl is-enabled lightdm 2>/dev/null || true)
|
|
556
|
+
if [[ "$ACTIVE" == "active" ]]; then
|
|
557
|
+
ok "LightDM: RUNNING (auto-start: ${ENABLED})"
|
|
558
|
+
else
|
|
559
|
+
info "LightDM: ${ACTIVE} (auto-start: ${ENABLED})"
|
|
560
|
+
fi
|
|
561
|
+
if command -v tvservice &>/dev/null; then
|
|
562
|
+
info "HDMI: $(tvservice -s 2>/dev/null | head -1)"
|
|
563
|
+
fi
|
|
564
|
+
echo ""
|
|
565
|
+
;;
|
|
566
|
+
*)
|
|
567
|
+
echo "Usage: plc_checkweigher display [on|off|status]"
|
|
568
|
+
;;
|
|
569
|
+
esac
|
|
570
|
+
;;
|
|
571
|
+
|
|
572
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
573
|
+
# SMB CONFIG — interactive update of delivery target
|
|
574
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
575
|
+
smb-config)
|
|
576
|
+
prompt_smb_config
|
|
577
|
+
;;
|
|
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
|
+
|
|
740
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
741
|
+
# HELP
|
|
742
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
743
|
+
help|--help|-h)
|
|
744
|
+
echo ""
|
|
745
|
+
echo -e "${B} plc_checkweigher${NC} — PLC Check-Weigher system CLI"
|
|
746
|
+
echo ""
|
|
747
|
+
echo -e " ${W}Diagnostics${NC}"
|
|
748
|
+
echo " status Full system diagnostic — all checks + fix hints"
|
|
749
|
+
echo " logs Stream live logs (plc_watcher + plc_web)"
|
|
750
|
+
echo " queue Show SMB pending queue and delivery ledger"
|
|
751
|
+
echo ""
|
|
752
|
+
echo -e " ${W}Services${NC}"
|
|
753
|
+
echo " start Start plc_watcher and plc_web"
|
|
754
|
+
echo " stop Stop both services"
|
|
755
|
+
echo " restart Restart both services"
|
|
756
|
+
echo ""
|
|
757
|
+
echo -e " ${W}Network${NC}"
|
|
758
|
+
echo " wifi Scan and switch WiFi network"
|
|
759
|
+
echo " hotspot on [SSID] [PASS] Start WiFi hotspot (direct PC connection)"
|
|
760
|
+
echo " hotspot off Stop hotspot"
|
|
761
|
+
echo " hotspot status Show hotspot state"
|
|
762
|
+
echo " hotspot scan Detect connected PCs → set as SMB target"
|
|
763
|
+
echo ""
|
|
764
|
+
echo -e " ${W}Configuration${NC}"
|
|
765
|
+
echo " smb-config Interactively update SMB delivery target"
|
|
766
|
+
echo " push-test Push latest PDF to SMB target now"
|
|
767
|
+
echo ""
|
|
768
|
+
echo -e " ${W}Display${NC}"
|
|
769
|
+
echo " display on Enable display (start LightDM)"
|
|
770
|
+
echo " display off Disable display (stop LightDM)"
|
|
771
|
+
echo " display status Show display state"
|
|
772
|
+
echo ""
|
|
773
|
+
echo -e " ${R} uninstall${NC} Remove everything setup.sh installed"
|
|
774
|
+
echo ""
|
|
775
|
+
;;
|
|
776
|
+
|
|
777
|
+
*)
|
|
778
|
+
err "Unknown command: $CMD"
|
|
779
|
+
echo " Run: plc_checkweigher help"
|
|
780
|
+
exit 1
|
|
781
|
+
;;
|
|
782
|
+
|
|
783
|
+
esac
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plc-checkweigher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.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/setup.sh
CHANGED
|
@@ -136,6 +136,21 @@ setup_dirs() {
|
|
|
136
136
|
ok "${REPORTS_DIR}"
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
# ── CLI tool — install plc_checkweigher command ───────────────────────────────
|
|
140
|
+
install_cli() {
|
|
141
|
+
step "CLI tool ..."
|
|
142
|
+
CLI_SRC="${INSTALL_DIR}/bin/plc_checkweigher"
|
|
143
|
+
CLI_DEST="/usr/local/bin/plc_checkweigher"
|
|
144
|
+
if [[ -f "${CLI_SRC}" ]]; then
|
|
145
|
+
chmod +x "${CLI_SRC}"
|
|
146
|
+
cp "${CLI_SRC}" "${CLI_DEST}"
|
|
147
|
+
ok "plc_checkweigher → ${CLI_DEST}"
|
|
148
|
+
ok "Run: plc_checkweigher status (full system diagnostic)"
|
|
149
|
+
else
|
|
150
|
+
warn "bin/plc_checkweigher not found — skipping CLI install"
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
139
154
|
# ── 5. WiFi — scan → pick → password ─────────────────────────────────────────
|
|
140
155
|
setup_wifi() {
|
|
141
156
|
step "WiFi Setup"
|
|
@@ -601,10 +616,11 @@ main() {
|
|
|
601
616
|
setup_repo # 2
|
|
602
617
|
setup_venv # 3
|
|
603
618
|
setup_dirs # 4
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
619
|
+
install_cli # 5 — plc_checkweigher status command
|
|
620
|
+
setup_wifi # 6 — interactive WiFi picker
|
|
621
|
+
setup_smb # 7 — interactive SMB config → smb_config.py
|
|
622
|
+
setup_network_online # 8
|
|
623
|
+
install_services # 9
|
|
608
624
|
setup_boot_logo # 9 — Plymouth: logo + "SAI SAMARTH ENGG"
|
|
609
625
|
setup_display # 10 — LightDM priority, CPU isolation, utmpx, GPU
|
|
610
626
|
install_rt_kernel # 11 — LAST, so only one reboot needed
|
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
|