refine post-install and hardware GPU docs, Monitor UX and CLI styling

- rewrite the 15 post-install pages and the 3 hardware GPU pages so they reflect the current scripts (reversibility, tracked-tool counts, kernel parameters, per-tool commands, Alpine LXC propagation flow)
- migrate the legacy step-badge helper on post-install/optional and create-vm/synology to the canonical pill component, with the stepLabel key added in each locale
- fix rich-text i18n calls missing helpers across network, automated, optional, security, customization and the post-install landing pages, and escape the `<iface>` placeholder in automated so intl no longer parses it as a tag
- remove the mouse-follow blue overlay from the docs landing layout
- reposition the App-tab Edit button and stack the Search and Register controls vertically on mobile
- move the Bulk update Configure/Edit control into the section header so it behaves the same on desktop and mobile
- show a spinner during the final autoremove/autoclean pass of update-pve-safe so the cleanup step reads as active instead of silent
- restyle the shell spinner and msg_info in a distinctive purple and drop the unused msg_lang duplicate
- add a web-docs i18n build script and its CI workflow, plus tests for the pushover notification channel
This commit is contained in:
MacRimi
2026-08-26 17:23:09 +02:00
parent b71dd65898
commit fcfe8da765
106 changed files with 3376 additions and 1358 deletions
+114 -6
View File
@@ -1055,10 +1055,13 @@ PROXMOX_CUSTOM_CERT_PATH = "/etc/pve/local/pveproxy-ssl.pem"
PROXMOX_CUSTOM_KEY_PATH = "/etc/pve/local/pveproxy-ssl.key"
_SSL_RUNTIME_LOCK = threading.RLock()
_SSL_RUNTIME_REFRESH_LOCK = threading.Lock()
_SSL_RUNTIME_CONTEXT = None
_SSL_RUNTIME_FINGERPRINT = ""
_SSL_RUNTIME_CERT_PATH = ""
_SSL_RUNTIME_KEY_PATH = ""
_SSL_RUNTIME_SOURCE = "none"
_SSL_RUNTIME_LAST_REFRESH_ERROR = ""
def load_ssl_config():
@@ -1100,6 +1103,15 @@ def save_ssl_config(config):
return False
def _detect_proxmox_certificate_paths():
"""Return the certificate pair currently preferred by Proxmox."""
if os.path.isfile(PROXMOX_CUSTOM_CERT_PATH) and os.path.isfile(PROXMOX_CUSTOM_KEY_PATH):
return PROXMOX_CUSTOM_CERT_PATH, PROXMOX_CUSTOM_KEY_PATH
if os.path.isfile(PROXMOX_CERT_PATH) and os.path.isfile(PROXMOX_KEY_PATH):
return PROXMOX_CERT_PATH, PROXMOX_KEY_PATH
return "", ""
def detect_proxmox_certificates():
"""
Detect available Proxmox certificates.
@@ -1117,11 +1129,10 @@ def detect_proxmox_certificates():
"cert_info": None
}
if os.path.isfile(PROXMOX_CUSTOM_CERT_PATH) and os.path.isfile(PROXMOX_CUSTOM_KEY_PATH):
result["proxmox_cert"] = PROXMOX_CUSTOM_CERT_PATH
result["proxmox_key"] = PROXMOX_CUSTOM_KEY_PATH
result["proxmox_available"] = True
elif os.path.isfile(PROXMOX_CERT_PATH) and os.path.isfile(PROXMOX_KEY_PATH):
cert_path, key_path = _detect_proxmox_certificate_paths()
if cert_path and key_path:
result["proxmox_cert"] = cert_path
result["proxmox_key"] = key_path
result["proxmox_available"] = True
if result["proxmox_available"]:
@@ -1209,17 +1220,112 @@ def _build_server_ssl_context(cert_path, key_path):
return context
def _record_ssl_refresh_error(error):
"""Log one warning per distinct automatic-refresh failure."""
global _SSL_RUNTIME_LAST_REFRESH_ERROR
message = str(error)
with _SSL_RUNTIME_LOCK:
if message == _SSL_RUNTIME_LAST_REFRESH_ERROR:
return
_SSL_RUNTIME_LAST_REFRESH_ERROR = message
print(
"[ProxMenux] Proxmox TLS certificate refresh skipped; "
f"the active certificate remains unchanged: {message}",
flush=True,
)
def _persist_active_proxmox_certificate_paths(cert_path, key_path):
"""Keep the selected Proxmox pair in sync for the next service start."""
config = load_ssl_config()
if not config.get("enabled") or config.get("source") != "proxmox":
return
if config.get("cert_path") == cert_path and config.get("key_path") == key_path:
return
updated_config = dict(config)
updated_config["cert_path"] = cert_path
updated_config["key_path"] = key_path
if not save_ssl_config(updated_config):
print(
"[ProxMenux] Warning: the renewed Proxmox certificate is active, "
"but its paths could not be saved for the next service start",
flush=True,
)
def _refresh_proxmox_ssl_context_for_handshake():
"""Activate a renewed Proxmox pair just before a TLS handshake.
This deliberately has no timer and does not depend on inotify (pmxcfs can
update /etc/pve without emitting a local event). The small PEM pair is
inspected only when a client starts a new TLS connection. Any missing,
partial or mismatched pair leaves the already-active context untouched.
"""
global _SSL_RUNTIME_LAST_REFRESH_ERROR
with _SSL_RUNTIME_LOCK:
if _SSL_RUNTIME_SOURCE != "proxmox" or _SSL_RUNTIME_CONTEXT is None:
return False
# Several browser connections can arrive together. Only one of them may
# validate/swap a newly written pair; the others reuse its result.
with _SSL_RUNTIME_REFRESH_LOCK:
with _SSL_RUNTIME_LOCK:
if _SSL_RUNTIME_SOURCE != "proxmox" or _SSL_RUNTIME_CONTEXT is None:
return False
active_fingerprint = _SSL_RUNTIME_FINGERPRINT
active_cert_path = _SSL_RUNTIME_CERT_PATH
active_key_path = _SSL_RUNTIME_KEY_PATH
cert_path, key_path = _detect_proxmox_certificate_paths()
if not cert_path or not key_path:
raise RuntimeError("No complete Proxmox certificate/key pair was detected")
candidate_fingerprint = _certificate_pair_fingerprint(cert_path, key_path)
paths_changed = cert_path != active_cert_path or key_path != active_key_path
if candidate_fingerprint == active_fingerprint and not paths_changed:
return False
# reload_server_ssl_context builds and validates the replacement first
# and checks that neither PEM changed while it was being loaded. The
# global context is swapped only after all of those checks succeed.
changed = reload_server_ssl_context(cert_path, key_path)
_persist_active_proxmox_certificate_paths(cert_path, key_path)
with _SSL_RUNTIME_LOCK:
_SSL_RUNTIME_LAST_REFRESH_ERROR = ""
if changed:
print(
f"[ProxMenux] Renewed Proxmox TLS certificate activated from {cert_path}",
flush=True,
)
return changed
def create_reloadable_ssl_context(cert_path, key_path):
"""Create the server context and register it for manual hot reloads."""
"""Create the stable server context used by automatic and manual reloads."""
global _SSL_RUNTIME_CONTEXT
global _SSL_RUNTIME_FINGERPRINT
global _SSL_RUNTIME_CERT_PATH
global _SSL_RUNTIME_KEY_PATH
global _SSL_RUNTIME_SOURCE
global _SSL_RUNTIME_LAST_REFRESH_ERROR
context = _build_server_ssl_context(cert_path, key_path)
fingerprint = _certificate_pair_fingerprint(cert_path, key_path)
config = load_ssl_config()
source = config.get("source", "none") if config.get("enabled") else "none"
def _select_active_context(ssl_socket, _server_name, _initial_context):
try:
_refresh_proxmox_ssl_context_for_handshake()
except Exception as error:
# Never fail a client handshake because Proxmox is between the
# certificate and key writes. The previously validated context
# remains authoritative until a later connection can load both.
_record_ssl_refresh_error(error)
with _SSL_RUNTIME_LOCK:
active_context = _SSL_RUNTIME_CONTEXT
if active_context is not None and ssl_socket.context is not active_context:
@@ -1231,6 +1337,8 @@ def create_reloadable_ssl_context(cert_path, key_path):
_SSL_RUNTIME_FINGERPRINT = fingerprint
_SSL_RUNTIME_CERT_PATH = cert_path
_SSL_RUNTIME_KEY_PATH = key_path
_SSL_RUNTIME_SOURCE = source
_SSL_RUNTIME_LAST_REFRESH_ERROR = ""
return context