Files
CyberPanel/pluginHolder/management/commands/refresh_plugin_store_cache.py
master3395 44263a37a9 pluginHolder: fix plugin store stale-cache refresh + hourly scheduler
Remove stuck plugin-store refresh locks, show correct cache status in UI, and add a management command for hourly refresh.
2026-03-26 22:45:54 +01:00

103 lines
3.7 KiB
Python

# -*- coding: utf-8 -*-
import os
import time
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "Refresh CyberPanel plugin store cache (hourly scheduler)."
def add_arguments(self, parser):
parser.add_argument(
"--force",
action="store_true",
help="Refresh even if cache is not expired.",
)
parser.add_argument(
"--stale-lock-seconds",
type=int,
default=900,
help="Remove the cache-refresh lock if it is older than this many seconds.",
)
def handle(self, *args, **options):
force = bool(options.get("force", False))
stale_lock_seconds = int(options.get("stale_lock_seconds", 900))
try:
from pluginHolder import views as plugin_views
except Exception as e:
# Avoid printing secrets; just show a minimal message.
self.stderr.write("Failed to import pluginHolder views for cache refresh.")
return 1
# Only refresh when needed (unless --force is used).
try:
cache_expiry_timestamp, _ = plugin_views._get_cache_expiry_time()
cache_expired = plugin_views._is_cache_expired(cache_expiry_timestamp)
except Exception:
cache_expired = True
if not force and cache_expiry_timestamp and not cache_expired:
self.stdout.write("Plugin store cache is still fresh; no refresh needed.")
return 0
lock_path = plugin_views.PLUGIN_STORE_REFRESH_LOCK_FILE
try:
plugin_views._ensure_cache_dir()
except Exception:
pass
# Remove stale lock left behind by a crashed/aborted refresh.
if os.path.exists(lock_path):
try:
age_s = time.time() - os.path.getmtime(lock_path)
if age_s > stale_lock_seconds:
os.remove(lock_path)
try:
plugin_views.logging.writeToFile(
f"Management refresh: removed stale plugin store refresh lock (age: {age_s:.0f}s)"
)
except Exception:
pass
except Exception:
pass
# Acquire lock to avoid stampedes when multiple instances refresh.
try:
fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
with os.fdopen(fd, "w") as f:
f.write(str(os.getpid()))
except FileExistsError:
self.stdout.write("Plugin store refresh skipped: lock already exists.")
return 0
except Exception:
self.stderr.write("Plugin store refresh failed: could not acquire refresh lock.")
return 1
try:
plugins = plugin_views._fetch_plugins_from_github()
if not plugins:
self.stdout.write("Plugin store refresh fetched 0 plugins; cache not updated.")
return 0
plugin_views._save_plugins_cache(plugins)
self.stdout.write(f"Plugin store cache refreshed successfully. plugins={len(plugins)}")
return 0
except Exception as e:
# Log error summary server-side; don't leak internal exception details to stdout.
try:
plugin_views.logging.writeToFile(f"Plugin store cache refresh failed: {str(e)}")
except Exception:
pass
self.stderr.write("Plugin store cache refresh failed. Check error logs.")
return 1
finally:
try:
if os.path.exists(lock_path):
os.remove(lock_path)
except Exception:
pass