kill-subscription.sh
· 6.9 KiB · Bash
Raw
#!/usr/bin/env bash
set -Eeuo pipefail
# proxmox-subscription-popup.sh
#
# Safe Proxmox VE 9 popup suppressor with:
# - specific patch only
# - no repair unless --repair is passed
# - colored output
# - backup before patch
# - automatic rollback if patch verification fails
#
# Usage:
# sudo bash proxmox-subscription-popup.sh
# sudo bash proxmox-subscription-popup.sh --undo
# sudo bash proxmox-subscription-popup.sh --repair
# sudo bash proxmox-subscription-popup.sh --status
SCRIPT_NAME="$(basename "$0")"
JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js"
STATE_DIR="/root/.proxmox-subscription-popup"
BACKUP_FILE="${STATE_DIR}/proxmoxlib.js.bak"
PATCH_FILE="${STATE_DIR}/subscription-popup.patch"
DO_REPAIR=0
DO_UNDO=0
DO_STATUS=0
for arg in "$@"; do
case "$arg" in
--repair) DO_REPAIR=1 ;;
--undo) DO_UNDO=1 ;;
--status) DO_STATUS=1 ;;
-h|--help)
cat <<'EOF'
Usage:
sudo bash proxmox-subscription-popup.sh Apply the patch
sudo bash proxmox-subscription-popup.sh --undo Restore backup if present
sudo bash proxmox-subscription-popup.sh --repair Reinstall owning package, restart proxy
sudo bash proxmox-subscription-popup.sh --status Show current state
EOF
exit 0
;;
*)
echo "Unknown argument: $arg" >&2
exit 2
;;
esac
done
if [[ "${EUID}" -ne 0 ]]; then
echo "Please run as root." >&2
exit 1
fi
mkdir -p "$STATE_DIR"
if [[ -t 1 ]]; then
C_RESET=$'\033[0m'
C_BOLD=$'\033[1m'
C_DIM=$'\033[2m'
C_RED=$'\033[1;31m'
C_GREEN=$'\033[1;32m'
C_YELLOW=$'\033[1;33m'
C_BLUE=$'\033[1;34m'
C_MAGENTA=$'\033[1;35m'
C_CYAN=$'\033[1;36m'
else
C_RESET=""
C_BOLD=""
C_DIM=""
C_RED=""
C_GREEN=""
C_YELLOW=""
C_BLUE=""
C_MAGENTA=""
C_CYAN=""
fi
say() { printf "%b\n" "$*"; }
info() { say "${C_CYAN}▶${C_RESET} $*"; }
ok() { say "${C_GREEN}✔${C_RESET} $*"; }
warn() { say "${C_YELLOW}⚠${C_RESET} $*"; }
fail() { say "${C_RED}✖${C_RESET} $*" >&2; }
title() { say "${C_BOLD}${C_MAGENTA}$*${C_RESET}"; }
restart_proxy() {
info "Restarting pveproxy..."
systemctl restart pveproxy
ok "pveproxy restarted"
}
show_browser_hint() {
say
say "${C_BOLD}Browser refresh:${C_RESET} hard refresh the UI after this."
say "Mac: ${C_DIM}Cmd+Shift+R${C_RESET}"
say "Other: ${C_DIM}Ctrl+Shift+R${C_RESET}"
say
}
detect_owner_pkg() {
dpkg -S "$JS_FILE" 2>/dev/null | head -n1 | cut -d: -f1
}
is_proxmox() {
command -v pveversion >/dev/null 2>&1
}
version_major() {
if ! command -v pveversion >/dev/null 2>&1; then
return 1
fi
pveversion | sed -n 's/^pve-manager\/\([0-9]\+\)\..*/\1/p' | head -n1
}
is_patched() {
grep -Fq "orig_cmd(); return;" "$JS_FILE"
}
has_expected_original() {
grep -Fq "if (data.status.toLowerCase() !== 'active')" "$JS_FILE" &&
grep -Fq "Ext.Msg.show({" "$JS_FILE"
}
write_patch_file() {
cat > "$PATCH_FILE" <<'EOF'
--- proxmoxlib.js.orig
+++ proxmoxlib.js
@@ -1,7 +1,8 @@
- checked_command: function(orig_cmd) {
+ checked_command: function(orig_cmd) {
+ orig_cmd(); return;
Proxmox.Utils.API2Request({
url: '/nodes/localhost/subscription',
method: 'GET',
failure: function(response, opts) {
Ext.Msg.alert(gettext('Error'), response.htmlStatus);
},
EOF
}
status() {
title "Proxmox subscription popup status"
if ! is_proxmox; then
fail "This does not look like a Proxmox host."
exit 1
fi
local major
major="$(version_major || true)"
if [[ -n "$major" ]]; then
info "Detected Proxmox major version: ${C_BOLD}${major}${C_RESET}"
fi
if [[ ! -f "$JS_FILE" ]]; then
fail "JS file not found: $JS_FILE"
exit 1
fi
local owner
owner="$(detect_owner_pkg || true)"
if [[ -n "$owner" ]]; then
info "Owning package: ${C_BOLD}${owner}${C_RESET}"
fi
if is_patched; then
ok "Popup suppression patch appears to be installed."
else
warn "Popup suppression patch does not appear to be installed."
fi
if [[ -f "$BACKUP_FILE" ]]; then
info "Backup present: ${C_BOLD}${BACKUP_FILE}${C_RESET}"
else
warn "No backup found in ${STATE_DIR}"
fi
}
repair() {
title "Repairing Proxmox web UI file"
[[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; }
local owner
owner="$(detect_owner_pkg || true)"
[[ -n "$owner" ]] || { fail "Could not determine owning package for $JS_FILE"; exit 1; }
info "Reinstalling package: ${C_BOLD}${owner}${C_RESET}"
apt-get update
apt-get install --reinstall -y "$owner"
[[ -f "$JS_FILE" ]] || { fail "File still missing after reinstall"; exit 1; }
restart_proxy
ok "Repair complete"
show_browser_hint
}
undo() {
title "Restoring original file"
[[ -f "$BACKUP_FILE" ]] || { fail "No backup found at $BACKUP_FILE"; exit 1; }
[[ -f "$JS_FILE" ]] || { fail "Target file missing: $JS_FILE"; exit 1; }
cp -f "$BACKUP_FILE" "$JS_FILE"
ok "Original file restored from backup"
restart_proxy
show_browser_hint
}
apply_patch() {
title "Applying safe Proxmox popup patch"
[[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; }
if ! is_proxmox; then
fail "This does not look like a Proxmox host."
exit 1
fi
local major
major="$(version_major || true)"
if [[ "$major" != "9" ]]; then
warn "This script was requested for Proxmox 9. Detected major version: ${major:-unknown}"
warn "Continuing only if the target code still matches exactly."
else
ok "Detected Proxmox VE 9"
fi
if is_patched; then
ok "Patch is already installed. Nothing to do."
show_browser_hint
return 0
fi
if ! has_expected_original; then
fail "The expected original code pattern was not found."
fail "Refusing to patch, because the file layout is not what this script expects."
fail "Use --repair first if the file is damaged, or inspect the current file manually."
exit 1
fi
if [[ ! -f "$BACKUP_FILE" ]]; then
info "Creating backup at ${C_BOLD}${BACKUP_FILE}${C_RESET}"
cp -a "$JS_FILE" "$BACKUP_FILE"
ok "Backup created"
else
warn "Backup already exists, leaving it untouched"
fi
local tmp
tmp="$(mktemp)"
cp -a "$JS_FILE" "$tmp"
write_patch_file
info "Checking whether patch applies cleanly..."
if ! patch --dry-run "$tmp" "$PATCH_FILE" >/dev/null 2>&1; then
rm -f "$tmp"
fail "Patch dry-run failed. File is not an exact match for this patch."
fail "No changes were made."
exit 1
fi
ok "Patch dry-run succeeded"
info "Applying patch..."
patch "$JS_FILE" "$PATCH_FILE" >/dev/null
ok "Patch applied"
if ! is_patched; then
warn "Verification failed after patch; restoring backup"
cp -f "$BACKUP_FILE" "$JS_FILE"
rm -f "$tmp"
fail "Patch did not verify cleanly. Original file restored."
exit 1
fi
rm -f "$tmp"
restart_proxy
ok "Subscription popup suppression is in place"
show_browser_hint
}
case 1 in
$DO_STATUS) status ;;
$DO_UNDO) undo ;;
$DO_REPAIR) repair ;;
*) apply_patch ;;
esac
| 1 | #!/usr/bin/env bash |
| 2 | set -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 | |
| 19 | SCRIPT_NAME="$(basename "$0")" |
| 20 | JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js" |
| 21 | STATE_DIR="/root/.proxmox-subscription-popup" |
| 22 | BACKUP_FILE="${STATE_DIR}/proxmoxlib.js.bak" |
| 23 | PATCH_FILE="${STATE_DIR}/subscription-popup.patch" |
| 24 | |
| 25 | DO_REPAIR=0 |
| 26 | DO_UNDO=0 |
| 27 | DO_STATUS=0 |
| 28 | |
| 29 | for 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' |
| 36 | Usage: |
| 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 |
| 41 | EOF |
| 42 | exit 0 |
| 43 | ;; |
| 44 | *) |
| 45 | echo "Unknown argument: $arg" >&2 |
| 46 | exit 2 |
| 47 | ;; |
| 48 | esac |
| 49 | done |
| 50 | |
| 51 | if [[ "${EUID}" -ne 0 ]]; then |
| 52 | echo "Please run as root." >&2 |
| 53 | exit 1 |
| 54 | fi |
| 55 | |
| 56 | mkdir -p "$STATE_DIR" |
| 57 | |
| 58 | if [[ -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' |
| 68 | else |
| 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="" |
| 78 | fi |
| 79 | |
| 80 | say() { printf "%b\n" "$*"; } |
| 81 | info() { say "${C_CYAN}▶${C_RESET} $*"; } |
| 82 | ok() { say "${C_GREEN}✔${C_RESET} $*"; } |
| 83 | warn() { say "${C_YELLOW}⚠${C_RESET} $*"; } |
| 84 | fail() { say "${C_RED}✖${C_RESET} $*" >&2; } |
| 85 | title() { say "${C_BOLD}${C_MAGENTA}$*${C_RESET}"; } |
| 86 | |
| 87 | restart_proxy() { |
| 88 | info "Restarting pveproxy..." |
| 89 | systemctl restart pveproxy |
| 90 | ok "pveproxy restarted" |
| 91 | } |
| 92 | |
| 93 | show_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 | |
| 101 | detect_owner_pkg() { |
| 102 | dpkg -S "$JS_FILE" 2>/dev/null | head -n1 | cut -d: -f1 |
| 103 | } |
| 104 | |
| 105 | is_proxmox() { |
| 106 | command -v pveversion >/dev/null 2>&1 |
| 107 | } |
| 108 | |
| 109 | version_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 | |
| 116 | is_patched() { |
| 117 | grep -Fq "orig_cmd(); return;" "$JS_FILE" |
| 118 | } |
| 119 | |
| 120 | has_expected_original() { |
| 121 | grep -Fq "if (data.status.toLowerCase() !== 'active')" "$JS_FILE" && |
| 122 | grep -Fq "Ext.Msg.show({" "$JS_FILE" |
| 123 | } |
| 124 | |
| 125 | write_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 | }, |
| 139 | EOF |
| 140 | } |
| 141 | |
| 142 | status() { |
| 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 | |
| 180 | repair() { |
| 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 | |
| 200 | undo() { |
| 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 | |
| 213 | apply_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 | |
| 287 | case 1 in |
| 288 | $DO_STATUS) status ;; |
| 289 | $DO_UNDO) undo ;; |
| 290 | $DO_REPAIR) repair ;; |
| 291 | *) apply_patch ;; |
| 292 | esac |