kill-subscription.sh
· 8.1 KiB · Bash
Raw
#!/usr/bin/env bash
set -Eeuo pipefail
# proxmox-subscription-popup.sh
#
# Safe Proxmox VE 9 subscription popup suppressor.
#
# Defaults:
# - patch only
# - no repair unless --repair is passed
# - refuses to patch unless file matches expected structure
#
# Usage:
# sudo bash proxmox-subscription-popup.sh
# sudo bash proxmox-subscription-popup.sh --status
# sudo bash proxmox-subscription-popup.sh --undo
# sudo bash proxmox-subscription-popup.sh --repair
#
# Notes:
# - Uses `patch` with a very specific diff
# - Creates a backup before patching
# - Restarts pveproxy after changes
# - Any package update may restore the original file
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"
WORK_ORIG="${STATE_DIR}/proxmoxlib.js.orig"
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 popup suppression patch
sudo bash proxmox-subscription-popup.sh --status
Show current state
sudo bash proxmox-subscription-popup.sh --undo
Restore the original file from backup
sudo bash proxmox-subscription-popup.sh --repair
Reinstall the package that owns proxmoxlib.js and restart pveproxy
EOF
exit 0
;;
*)
echo "Unknown argument: $arg" >&2
exit 2
;;
esac
done
if [ "$(id -u)" -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}"; }
trap 'fail "Script failed on line ${LINENO}. No further changes will be made."' ERR
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
}
require_file() {
[[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; }
}
has_patch_tool() {
command -v patch >/dev/null 2>&1
}
ensure_patch_tool() {
if has_patch_tool; then
return 0
fi
fail "'patch' command not found."
fail "Install it with: apt-get update && apt-get install -y patch"
exit 1
}
# Very specific to the function structure from the uploaded file.
has_expected_original() {
grep -Fq "checked_command: function (orig_cmd) {" "$JS_FILE" &&
grep -Fq "url: '/nodes/localhost/subscription'" "$JS_FILE" &&
grep -Fq "res.data.status.toLowerCase() !== 'active'" "$JS_FILE" &&
grep -Fq "title: gettext('No valid subscription')" "$JS_FILE"
}
# Detects our injected short-circuit immediately after the function opens.
is_patched() {
python3 - <<'PY' "$JS_FILE"
import re, sys
path = sys.argv[1]
text = open(path, 'r', encoding='utf-8').read()
pat = re.compile(
r"checked_command:\s*function\s*\(orig_cmd\)\s*\{\s*orig_cmd\(\);\s*return;\s*Proxmox\.Utils\.API2Request\(",
re.S,
)
sys.exit(0 if pat.search(text) else 1)
PY
}
write_patch_file() {
cat > "$PATCH_FILE" <<'EOF'
--- proxmoxlib.js.orig
+++ proxmoxlib.js
@@ -1,5 +1,7 @@
checked_command: function (orig_cmd) {
+ orig_cmd();
+ return;
Proxmox.Utils.API2Request({
url: '/nodes/localhost/subscription',
method: 'GET',
EOF
}
status() {
title "Proxmox subscription popup status"
if ! is_proxmox; then
fail "This does not look like a Proxmox host."
exit 1
fi
require_file
local major owner
major="$(version_major || true)"
owner="$(detect_owner_pkg || true)"
if [[ -n "$major" ]]; then
info "Detected Proxmox major version: ${C_BOLD}${major}${C_RESET}"
else
warn "Could not determine Proxmox major version"
fi
if [[ -n "$owner" ]]; then
info "Owning package: ${C_BOLD}${owner}${C_RESET}"
else
warn "Could not determine owning package"
fi
if has_expected_original; then
ok "Expected original popup code pattern is present"
else
warn "Expected original popup code pattern is NOT present"
fi
if is_patched; then
ok "Patch appears to be installed"
else
warn "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 at ${STATE_DIR}"
fi
}
repair() {
title "Repairing Proxmox web UI file"
require_file
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"
require_file
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; }
require_file
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"
require_file
ensure_patch_tool
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
ok "Detected Proxmox VE 9"
else
warn "Expected Proxmox VE 9, detected: ${major:-unknown}"
warn "Proceeding only if the file matches the exact expected structure"
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, then try again."
exit 1
fi
ok "Expected original code pattern found"
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
cp -a "$JS_FILE" "$WORK_ORIG"
write_patch_file
info "Testing patch with dry-run..."
if ! (cd "$STATE_DIR" && patch --dry-run proxmoxlib.js.orig subscription-popup.patch >/dev/null 2>&1); then
fail "Patch dry-run failed."
fail "No changes were made."
exit 1
fi
ok "Patch dry-run succeeded"
info "Applying patch..."
cp -a "$JS_FILE" "$WORK_ORIG"
if ! (cd "$STATE_DIR" && patch "$WORK_ORIG" subscription-popup.patch >/dev/null 2>&1); then
fail "Patch command failed unexpectedly."
exit 1
fi
cp -f "$WORK_ORIG" "$JS_FILE"
if ! is_patched; then
warn "Verification failed after patch; restoring backup"
cp -f "$BACKUP_FILE" "$JS_FILE"
fail "Patch did not verify cleanly. Original file restored."
exit 1
fi
ok "Patch verified successfully"
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 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 | |
| 25 | SCRIPT_NAME="$(basename "$0")" |
| 26 | JS_FILE="/usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js" |
| 27 | STATE_DIR="/root/.proxmox-subscription-popup" |
| 28 | BACKUP_FILE="${STATE_DIR}/proxmoxlib.js.bak" |
| 29 | PATCH_FILE="${STATE_DIR}/subscription-popup.patch" |
| 30 | WORK_ORIG="${STATE_DIR}/proxmoxlib.js.orig" |
| 31 | |
| 32 | DO_REPAIR=0 |
| 33 | DO_UNDO=0 |
| 34 | DO_STATUS=0 |
| 35 | |
| 36 | for 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' |
| 43 | Usage: |
| 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 |
| 55 | EOF |
| 56 | exit 0 |
| 57 | ;; |
| 58 | *) |
| 59 | echo "Unknown argument: $arg" >&2 |
| 60 | exit 2 |
| 61 | ;; |
| 62 | esac |
| 63 | done |
| 64 | |
| 65 | if [ "$(id -u)" -ne 0 ]; then |
| 66 | echo "Please run as root." >&2 |
| 67 | exit 1 |
| 68 | fi |
| 69 | |
| 70 | mkdir -p "$STATE_DIR" |
| 71 | |
| 72 | if [[ -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' |
| 82 | else |
| 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="" |
| 92 | fi |
| 93 | |
| 94 | say() { printf "%b\n" "$*"; } |
| 95 | info() { say "${C_CYAN}▶${C_RESET} $*"; } |
| 96 | ok() { say "${C_GREEN}✔${C_RESET} $*"; } |
| 97 | warn() { say "${C_YELLOW}⚠${C_RESET} $*"; } |
| 98 | fail() { say "${C_RED}✖${C_RESET} $*" >&2; } |
| 99 | title() { say "${C_BOLD}${C_MAGENTA}$*${C_RESET}"; } |
| 100 | |
| 101 | trap 'fail "Script failed on line ${LINENO}. No further changes will be made."' ERR |
| 102 | |
| 103 | restart_proxy() { |
| 104 | info "Restarting pveproxy..." |
| 105 | systemctl restart pveproxy |
| 106 | ok "pveproxy restarted" |
| 107 | } |
| 108 | |
| 109 | show_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 | |
| 117 | detect_owner_pkg() { |
| 118 | dpkg -S "$JS_FILE" 2>/dev/null | head -n1 | cut -d: -f1 |
| 119 | } |
| 120 | |
| 121 | is_proxmox() { |
| 122 | command -v pveversion >/dev/null 2>&1 |
| 123 | } |
| 124 | |
| 125 | version_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 | |
| 132 | require_file() { |
| 133 | [[ -f "$JS_FILE" ]] || { fail "Missing file: $JS_FILE"; exit 1; } |
| 134 | } |
| 135 | |
| 136 | has_patch_tool() { |
| 137 | command -v patch >/dev/null 2>&1 |
| 138 | } |
| 139 | |
| 140 | ensure_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. |
| 150 | has_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. |
| 158 | is_patched() { |
| 159 | python3 - <<'PY' "$JS_FILE" |
| 160 | import re, sys |
| 161 | path = sys.argv[1] |
| 162 | text = open(path, 'r', encoding='utf-8').read() |
| 163 | pat = 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 | ) |
| 167 | sys.exit(0 if pat.search(text) else 1) |
| 168 | PY |
| 169 | } |
| 170 | |
| 171 | write_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', |
| 182 | EOF |
| 183 | } |
| 184 | |
| 185 | status() { |
| 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 | |
| 230 | repair() { |
| 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 | |
| 249 | undo() { |
| 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 | |
| 262 | apply_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 | |
| 337 | case 1 in |
| 338 | $DO_STATUS) status ;; |
| 339 | $DO_UNDO) undo ;; |
| 340 | $DO_REPAIR) repair ;; |
| 341 | *) apply_patch ;; |
| 342 | esac |