feat(scripts): run script executions in background and poll for completion

Community scripts (build.func) install packages like Java/OpenSearch and
can take minutes, well past the nginx/proxy HTTP timeout. execute_script
now returns immediately with status "running" and finishes the job on a
background thread with its own DB session; the frontend polls
/api/scripts/executions/{id} every 3s while running.

Also set TERM=xterm for script execution over SSH — community scripts
call `clear` and print colored output, which fails with "TERM
environment variable not set" when run without a PTY.
This commit is contained in:
markmorado
2026-07-23 21:59:18 +05:00
parent 761bd7bca8
commit 0b0b11c689
4 changed files with 86 additions and 19 deletions

View File

@@ -30,6 +30,13 @@ from ..ssh_client import SSHClient
_CMD_TIMEOUT = 60
# Community-скрипты (build.func и т.п.) вызывают `clear` и печатают цветной
# вывод, которым нужен установленный TERM. Панель запускает скрипт без PTY
# (get_pty=False), поэтому TERM не наследуется — и `clear` падает с
# "TERM environment variable not set", обрывая весь скрипт. Задаём безопасный
# дефолт префиксом команды (надёжнее env paramiko: sshd AcceptEnv его режет).
_SCRIPT_ENV = "TERM=xterm"
class ScriptExecError(RuntimeError):
"""Ошибка выполнения скрипта с человекочитаемым сообщением."""
@@ -97,7 +104,8 @@ def run_on_node(server: ProxmoxServer, client: ProxmoxClient, script: str,
try:
_run(ssh, f"printf '%s' '{b64}' | base64 -d > {tmp} && chmod +x {tmp}",
what="write script on node")
output, exit_code = ssh.execute(f"{interpreter} {tmp}", return_exit_code=True, timeout=timeout)
output, exit_code = ssh.execute(f"{_SCRIPT_ENV} {interpreter} {tmp}",
return_exit_code=True, timeout=timeout)
return ExecResult(success=exit_code == 0, output=output or "", exit_code=exit_code)
except ScriptExecError as e:
return ExecResult(success=False, output="", exit_code=-1, error=str(e))
@@ -124,7 +132,7 @@ def run_on_lxc(server: ProxmoxServer, client: ProxmoxClient, node: str, vmid: in
what="write script on node")
_run(ssh, f"pct push {vmid} {tmp_node} {tmp_ct}", what="pct push script")
output, exit_code = ssh.execute(
f"pct exec {vmid} -- {interpreter} {tmp_ct}",
f"pct exec {vmid} -- env {_SCRIPT_ENV} {interpreter} {tmp_ct}",
return_exit_code=True, timeout=timeout,
)
return ExecResult(success=exit_code == 0, output=output or "", exit_code=exit_code)