Last active 1 month ago

Revision bb7cafeb8f9c073ce6f816f7a2e91754fae62d76

kill-subscription.sh Raw
1#!/usr/bin/env bash
2set -Eeuo pipefail
3
4# proxmox-subscription-popup.sh
5#
6# Safe Proxmox VE 9 popup suppressor with:
7# - specific patch only
8# - no repair unless --repair is passed
9# - colored output
10# - backup before patch
11# - automatic rollback if patch verification fails
12#
13# Usage:
14# sudo bash proxmox-subscription-popup.sh
15# sudo bash proxmox-subscription-popup.sh --undo
16# sudo bash proxmox-subscription-popup.sh --repair
17# sudo bash proxmox-subscription-popup.sh --status
18
19SCRIPT_NAME="$(basename "$0")"
20JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
21STATE_DIR="/root/.proxmox-subscription-popup"
22BACKUP_FILE="${STATE_DIR}/proxmoxlib.js.bak"
23PATCH_FILE="${STATE_DIR}/subscription-popup.patch"
24
25DO_REPAIR=0
26DO_UNDO=0
27DO_STATUS=0
28
29for arg in "$@"; do
30 case "$arg" in
31 --repair) DO_REPAIR=1 ;;
32 --undo) DO_UNDO=1 ;;
33 --status) DO_STATUS=1 ;;
34 -h|--help)
35 cat <<'EOF'
36Usage:
37 sudo bash proxmox-subscription-popup.sh Apply the patch
38 sudo bash proxmox-subscription-popup.sh --undo Restore backup if present
39 sudo bash proxmox-subscription-popup.sh --repair Reinstall owning package, restart proxy
40 sudo bash proxmox-subscription-popup.sh --status Show current state
41EOF
42 exit 0
43 ;;
44 *)
45 echo "Unknown argument: $arg" >&2
46 exit 2
47 ;;
48 esac
49done
50
51if [[ "${EUID}" -ne 0 ]]; then
52 echo "Please run as root." >&2
53 exit 1
54fi
55
56mkdir -p "$STATE_DIR"
57
58if [[ -t 1 ]]; then
59 C_RESET=$'\033[0m'
60 C_BOLD=$'\033[1m'
61 C_DIM=$'\033[2m'
62 C_RED=$'\033[1;31m'
63 C_GREEN=$'\033[1;32m'
64 C_YELLOW=$'\033[1;33m'
65 C_BLUE=$'\033[1;34m'
66 C_MAGENTA=$'\033[1;35m'
67 C_CYAN=$'\033[1;36m'
68else
69 C_RESET=""
70 C_BOLD=""
71 C_DIM=""
72 C_RED=""
73 C_GREEN=""
74 C_YELLOW=""
75 C_BLUE=""
76 C_MAGENTA=""
77 C_CYAN=""
78fi
79
80say() { printf "%b\n" "$*"; }
81info() { say "${C_CYAN}${C_RESET} $*"; }
82ok() { say "${C_GREEN}${C_RESET} $*"; }
83warn() { say "${C_YELLOW}${C_RESET} $*"; }
84fail() { say "${C_RED}${C_RESET} $*" >&2; }
85title() { say "${C_BOLD}${C_MAGENTA}$*${C_RESET}"; }
86
87restart_proxy() {
88 info "Restarting pveproxy..."
89 systemctl restart pveproxy
90 ok "pveproxy restarted"
91}
92
93show_browser_hint() {
94 say
95 say "${C_BOLD}Browser refresh:${C_RESET} hard refresh the UI after this."
96 say "Mac: ${C_DIM}Cmd+Shift+R${C_RESET}"
97 say "Other: ${C_DIM}Ctrl+Shift+R${C_RESET}"
98 say
99}
100
101detect_owner_pkg() {
102 dpkg -S "$JS_FILE" 2>/dev/null | head -n1 | cut -d: -f1
103}
104
105is_proxmox() {
106 command -v pveversion >/dev/null 2>&1
107}
108
109version_major() {
110 if ! command -v pveversion >/dev/null 2>&1; then
111 return 1
112 fi
113 pveversion | sed -n 's/^pve-manager\/\([0-9]\+\)\..*/\1/p' | head -n1
114}
115
116is_patched() {
117 grep -Fq "orig_cmd(); return;" "$JS_FILE"
118}
119
120has_expected_original() {
121 grep -Fq "if (data.status.toLowerCase() !== 'active')" "$JS_FILE" &&
122 grep -Fq "Ext.Msg.show({" "$JS_FILE"
123}
124
125write_patch_file() {
126 cat > "$PATCH_FILE" <<'EOF'
127--- proxmoxlib.js.orig
128+++ proxmoxlib.js
129@@ -1,7 +1,8 @@
130- checked_command: function(orig_cmd) {
131+ checked_command: function(orig_cmd) {
132+ orig_cmd(); return;
133 Proxmox.Utils.API2Request({
134 url: '/nodes/localhost/subscription',
135 method: 'GET',
136 failure: function(response, opts) {
137 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
138 },
139EOF
140}
141
142status() {
143 title "Proxmox subscription popup status"
144
145 if ! is_proxmox; then
146 fail "This does not look like a Proxmox host."
147 exit 1
148 fi
149
150 local major
151 major="$(version_major || true)"
152 if [[ -n "$major" ]]; then
153 info "Detected Proxmox major version: ${C_BOLD}${major}${C_RESET}"
154 fi
155
156 if [[ ! -f "$JS_FILE" ]]; then
157 fail "JS file not found: $JS_FILE"
158 exit 1
159 fi
160
161 local owner
162 owner="$(detect_owner_pkg || true)"
163 if [[ -n "$owner" ]]; then
164 info "Owning package: ${C_BOLD}${owner}${C_RESET}"
165 fi
166
167 if is_patched; then
168 ok "Popup suppression patch appears to be installed."
169 else
170 warn "Popup suppression patch does not appear to be installed."
171 fi
172
173 if [[ -f "$BACKUP_FILE" ]]; then
174 info "Backup present: ${C_BOLD}${BACKUP_FILE}${C_RESET}"
175 else
176 warn "No backup found in ${STATE_DIR}"
177 fi
178}
179
180repair() {
181 title "Repairing Proxmox web UI file"
182
183 [[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; }
184
185 local owner
186 owner="$(detect_owner_pkg || true)"
187 [[ -n "$owner" ]] || { fail "Could not determine owning package for $JS_FILE"; exit 1; }
188
189 info "Reinstalling package: ${C_BOLD}${owner}${C_RESET}"
190 apt-get update
191 apt-get install --reinstall -y "$owner"
192
193 [[ -f "$JS_FILE" ]] || { fail "File still missing after reinstall"; exit 1; }
194
195 restart_proxy
196 ok "Repair complete"
197 show_browser_hint
198}
199
200undo() {
201 title "Restoring original file"
202
203 [[ -f "$BACKUP_FILE" ]] || { fail "No backup found at $BACKUP_FILE"; exit 1; }
204 [[ -f "$JS_FILE" ]] || { fail "Target file missing: $JS_FILE"; exit 1; }
205
206 cp -f "$BACKUP_FILE" "$JS_FILE"
207 ok "Original file restored from backup"
208
209 restart_proxy
210 show_browser_hint
211}
212
213apply_patch() {
214 title "Applying safe Proxmox popup patch"
215
216 [[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; }
217
218 if ! is_proxmox; then
219 fail "This does not look like a Proxmox host."
220 exit 1
221 fi
222
223 local major
224 major="$(version_major || true)"
225 if [[ "$major" != "9" ]]; then
226 warn "This script was requested for Proxmox 9. Detected major version: ${major:-unknown}"
227 warn "Continuing only if the target code still matches exactly."
228 else
229 ok "Detected Proxmox VE 9"
230 fi
231
232 if is_patched; then
233 ok "Patch is already installed. Nothing to do."
234 show_browser_hint
235 return 0
236 fi
237
238 if ! has_expected_original; then
239 fail "The expected original code pattern was not found."
240 fail "Refusing to patch, because the file layout is not what this script expects."
241 fail "Use --repair first if the file is damaged, or inspect the current file manually."
242 exit 1
243 fi
244
245 if [[ ! -f "$BACKUP_FILE" ]]; then
246 info "Creating backup at ${C_BOLD}${BACKUP_FILE}${C_RESET}"
247 cp -a "$JS_FILE" "$BACKUP_FILE"
248 ok "Backup created"
249 else
250 warn "Backup already exists, leaving it untouched"
251 fi
252
253 local tmp
254 tmp="$(mktemp)"
255 cp -a "$JS_FILE" "$tmp"
256
257 write_patch_file
258
259 info "Checking whether patch applies cleanly..."
260 if ! patch --dry-run "$tmp" "$PATCH_FILE" >/dev/null 2>&1; then
261 rm -f "$tmp"
262 fail "Patch dry-run failed. File is not an exact match for this patch."
263 fail "No changes were made."
264 exit 1
265 fi
266 ok "Patch dry-run succeeded"
267
268 info "Applying patch..."
269 patch "$JS_FILE" "$PATCH_FILE" >/dev/null
270 ok "Patch applied"
271
272 if ! is_patched; then
273 warn "Verification failed after patch; restoring backup"
274 cp -f "$BACKUP_FILE" "$JS_FILE"
275 rm -f "$tmp"
276 fail "Patch did not verify cleanly. Original file restored."
277 exit 1
278 fi
279
280 rm -f "$tmp"
281
282 restart_proxy
283 ok "Subscription popup suppression is in place"
284 show_browser_hint
285}
286
287case 1 in
288 $DO_STATUS) status ;;
289 $DO_UNDO) undo ;;
290 $DO_REPAIR) repair ;;
291 *) apply_patch ;;
292esac