Last active 1 month ago

Revision 6a1227648dc0e9cfb38d67ca97e8eeb62236ada3

kill-subscription.sh Raw
1#!/usr/bin/env bash
2set -Eeuo pipefail
3
4# proxmox-subscription-popup.sh
5#
6# Safe Proxmox VE 9 subscription popup suppressor.
7#
8# Defaults:
9# - patch only
10# - no repair unless --repair is passed
11# - refuses to patch unless file matches expected structure
12#
13# Usage:
14# sudo bash proxmox-subscription-popup.sh
15# sudo bash proxmox-subscription-popup.sh --status
16# sudo bash proxmox-subscription-popup.sh --undo
17# sudo bash proxmox-subscription-popup.sh --repair
18#
19# Notes:
20# - Uses `patch` with a very specific diff
21# - Creates a backup before patching
22# - Restarts pveproxy after changes
23# - Any package update may restore the original file
24
25SCRIPT_NAME="$(basename "$0")"
26JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
27STATE_DIR="/root/.proxmox-subscription-popup"
28BACKUP_FILE="${STATE_DIR}/proxmoxlib.js.bak"
29PATCH_FILE="${STATE_DIR}/subscription-popup.patch"
30WORK_ORIG="${STATE_DIR}/proxmoxlib.js.orig"
31
32DO_REPAIR=0
33DO_UNDO=0
34DO_STATUS=0
35
36for arg in "$@"; do
37 case "$arg" in
38 --repair) DO_REPAIR=1 ;;
39 --undo) DO_UNDO=1 ;;
40 --status) DO_STATUS=1 ;;
41 -h|--help)
42 cat <<'EOF'
43Usage:
44 sudo bash proxmox-subscription-popup.sh
45 Apply the popup suppression patch
46
47 sudo bash proxmox-subscription-popup.sh --status
48 Show current state
49
50 sudo bash proxmox-subscription-popup.sh --undo
51 Restore the original file from backup
52
53 sudo bash proxmox-subscription-popup.sh --repair
54 Reinstall the package that owns proxmoxlib.js and restart pveproxy
55EOF
56 exit 0
57 ;;
58 *)
59 echo "Unknown argument: $arg" >&2
60 exit 2
61 ;;
62 esac
63done
64
65if [ "$(id -u)" -ne 0 ]; then
66 echo "Please run as root." >&2
67 exit 1
68fi
69
70mkdir -p "$STATE_DIR"
71
72if [[ -t 1 ]]; then
73 C_RESET=$'\033[0m'
74 C_BOLD=$'\033[1m'
75 C_DIM=$'\033[2m'
76 C_RED=$'\033[1;31m'
77 C_GREEN=$'\033[1;32m'
78 C_YELLOW=$'\033[1;33m'
79 C_BLUE=$'\033[1;34m'
80 C_MAGENTA=$'\033[1;35m'
81 C_CYAN=$'\033[1;36m'
82else
83 C_RESET=""
84 C_BOLD=""
85 C_DIM=""
86 C_RED=""
87 C_GREEN=""
88 C_YELLOW=""
89 C_BLUE=""
90 C_MAGENTA=""
91 C_CYAN=""
92fi
93
94say() { printf "%b\n" "$*"; }
95info() { say "${C_CYAN}${C_RESET} $*"; }
96ok() { say "${C_GREEN}${C_RESET} $*"; }
97warn() { say "${C_YELLOW}${C_RESET} $*"; }
98fail() { say "${C_RED}${C_RESET} $*" >&2; }
99title() { say "${C_BOLD}${C_MAGENTA}$*${C_RESET}"; }
100
101trap 'fail "Script failed on line ${LINENO}. No further changes will be made."' ERR
102
103restart_proxy() {
104 info "Restarting pveproxy..."
105 systemctl restart pveproxy
106 ok "pveproxy restarted"
107}
108
109show_browser_hint() {
110 say
111 say "${C_BOLD}Browser refresh:${C_RESET} hard refresh the UI after this."
112 say "Mac: ${C_DIM}Cmd+Shift+R${C_RESET}"
113 say "Other: ${C_DIM}Ctrl+Shift+R${C_RESET}"
114 say
115}
116
117detect_owner_pkg() {
118 dpkg -S "$JS_FILE" 2>/dev/null | head -n1 | cut -d: -f1
119}
120
121is_proxmox() {
122 command -v pveversion >/dev/null 2>&1
123}
124
125version_major() {
126 if ! command -v pveversion >/dev/null 2>&1; then
127 return 1
128 fi
129 pveversion | sed -n 's/^pve-manager\/\([0-9]\+\)\..*/\1/p' | head -n1
130}
131
132require_file() {
133 [[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; }
134}
135
136has_patch_tool() {
137 command -v patch >/dev/null 2>&1
138}
139
140ensure_patch_tool() {
141 if has_patch_tool; then
142 return 0
143 fi
144 fail "'patch' command not found."
145 fail "Install it with: apt-get update && apt-get install -y patch"
146 exit 1
147}
148
149# Very specific to the function structure from the uploaded file.
150has_expected_original() {
151 grep -Fq "checked_command: function (orig_cmd) {" "$JS_FILE" &&
152 grep -Fq "url: '/nodes/localhost/subscription'" "$JS_FILE" &&
153 grep -Fq "res.data.status.toLowerCase() !== 'active'" "$JS_FILE" &&
154 grep -Fq "title: gettext('No valid subscription')" "$JS_FILE"
155}
156
157# Detects our injected short-circuit immediately after the function opens.
158is_patched() {
159 python3 - <<'PY' "$JS_FILE"
160import re, sys
161path = sys.argv[1]
162text = open(path, 'r', encoding='utf-8').read()
163pat = re.compile(
164 r"checked_command:\s*function\s*\(orig_cmd\)\s*\{\s*orig_cmd\(\);\s*return;\s*Proxmox\.Utils\.API2Request\(",
165 re.S,
166)
167sys.exit(0 if pat.search(text) else 1)
168PY
169}
170
171write_patch_file() {
172 cat > "$PATCH_FILE" <<'EOF'
173--- proxmoxlib.js.orig
174+++ proxmoxlib.js
175@@ -1,5 +1,7 @@
176 checked_command: function (orig_cmd) {
177+ orig_cmd();
178+ return;
179 Proxmox.Utils.API2Request({
180 url: '/nodes/localhost/subscription',
181 method: 'GET',
182EOF
183}
184
185status() {
186 title "Proxmox subscription popup status"
187
188 if ! is_proxmox; then
189 fail "This does not look like a Proxmox host."
190 exit 1
191 fi
192
193 require_file
194
195 local major owner
196 major="$(version_major || true)"
197 owner="$(detect_owner_pkg || true)"
198
199 if [[ -n "$major" ]]; then
200 info "Detected Proxmox major version: ${C_BOLD}${major}${C_RESET}"
201 else
202 warn "Could not determine Proxmox major version"
203 fi
204
205 if [[ -n "$owner" ]]; then
206 info "Owning package: ${C_BOLD}${owner}${C_RESET}"
207 else
208 warn "Could not determine owning package"
209 fi
210
211 if has_expected_original; then
212 ok "Expected original popup code pattern is present"
213 else
214 warn "Expected original popup code pattern is NOT present"
215 fi
216
217 if is_patched; then
218 ok "Patch appears to be installed"
219 else
220 warn "Patch does not appear to be installed"
221 fi
222
223 if [[ -f "$BACKUP_FILE" ]]; then
224 info "Backup present: ${C_BOLD}${BACKUP_FILE}${C_RESET}"
225 else
226 warn "No backup found at ${STATE_DIR}"
227 fi
228}
229
230repair() {
231 title "Repairing Proxmox web UI file"
232
233 require_file
234
235 local owner
236 owner="$(detect_owner_pkg || true)"
237 [[ -n "$owner" ]] || { fail "Could not determine owning package for $JS_FILE"; exit 1; }
238
239 info "Reinstalling package: ${C_BOLD}${owner}${C_RESET}"
240 apt-get update
241 apt-get install --reinstall -y "$owner"
242
243 require_file
244 restart_proxy
245 ok "Repair complete"
246 show_browser_hint
247}
248
249undo() {
250 title "Restoring original file"
251
252 [[ -f "$BACKUP_FILE" ]] || { fail "No backup found at $BACKUP_FILE"; exit 1; }
253 require_file
254
255 cp -f "$BACKUP_FILE" "$JS_FILE"
256 ok "Original file restored from backup"
257
258 restart_proxy
259 show_browser_hint
260}
261
262apply_patch() {
263 title "Applying safe Proxmox popup patch"
264
265 require_file
266 ensure_patch_tool
267
268 if ! is_proxmox; then
269 fail "This does not look like a Proxmox host."
270 exit 1
271 fi
272
273 local major
274 major="$(version_major || true)"
275 if [[ "$major" == "9" ]]; then
276 ok "Detected Proxmox VE 9"
277 else
278 warn "Expected Proxmox VE 9, detected: ${major:-unknown}"
279 warn "Proceeding only if the file matches the exact expected structure"
280 fi
281
282 if is_patched; then
283 ok "Patch is already installed. Nothing to do."
284 show_browser_hint
285 return 0
286 fi
287
288 if ! has_expected_original; then
289 fail "The expected original code pattern was not found."
290 fail "Refusing to patch, because the file layout is not what this script expects."
291 fail "Use --repair first if the file is damaged, then try again."
292 exit 1
293 fi
294 ok "Expected original code pattern found"
295
296 if [[ ! -f "$BACKUP_FILE" ]]; then
297 info "Creating backup at ${C_BOLD}${BACKUP_FILE}${C_RESET}"
298 cp -a "$JS_FILE" "$BACKUP_FILE"
299 ok "Backup created"
300 else
301 warn "Backup already exists, leaving it untouched"
302 fi
303
304 cp -a "$JS_FILE" "$WORK_ORIG"
305 write_patch_file
306
307 info "Testing patch with dry-run..."
308 if ! (cd "$STATE_DIR" && patch --dry-run proxmoxlib.js.orig subscription-popup.patch >/dev/null 2>&1); then
309 fail "Patch dry-run failed."
310 fail "No changes were made."
311 exit 1
312 fi
313 ok "Patch dry-run succeeded"
314
315 info "Applying patch..."
316 cp -a "$JS_FILE" "$WORK_ORIG"
317 if ! (cd "$STATE_DIR" && patch "$WORK_ORIG" subscription-popup.patch >/dev/null 2>&1); then
318 fail "Patch command failed unexpectedly."
319 exit 1
320 fi
321
322 cp -f "$WORK_ORIG" "$JS_FILE"
323
324 if ! is_patched; then
325 warn "Verification failed after patch; restoring backup"
326 cp -f "$BACKUP_FILE" "$JS_FILE"
327 fail "Patch did not verify cleanly. Original file restored."
328 exit 1
329 fi
330
331 ok "Patch verified successfully"
332 restart_proxy
333 ok "Subscription popup suppression is in place"
334 show_browser_hint
335}
336
337case 1 in
338 $DO_STATUS) status ;;
339 $DO_UNDO) undo ;;
340 $DO_REPAIR) repair ;;
341 *) apply_patch ;;
342esac