fix: update system now rebuilds docker image after git pull
- Add docker-compose package to Dockerfile (was missing, caused silent failure) - Auto-detect current git branch instead of hardcoding 'main' - Auto-select compose command (docker compose / docker-compose) - Write update logs to persistent /app/logs/update.log volume - Log git pull stdout/stderr for diagnostics
This commit is contained in:
@@ -40,6 +40,7 @@ RUN apk add --no-cache \
|
|||||||
curl \
|
curl \
|
||||||
git \
|
git \
|
||||||
docker-cli \
|
docker-cli \
|
||||||
|
docker-compose \
|
||||||
libstdc++ \
|
libstdc++ \
|
||||||
tzdata \
|
tzdata \
|
||||||
&& mkdir -p /root/.ssh /app/logs \
|
&& mkdir -p /root/.ssh /app/logs \
|
||||||
|
|||||||
@@ -401,82 +401,120 @@ async def perform_update() -> Dict[str, Any]:
|
|||||||
"completed": False
|
"completed": False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Persistent log file (survives via volume mount ./logs:/app/logs)
|
||||||
|
UPDATE_LOG = "/app/logs/update.log"
|
||||||
|
|
||||||
|
def _detect_branch() -> str:
|
||||||
|
"""Detect current git branch, falling back to 'main'"""
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
["git", "symbolic-ref", "--short", "HEAD"],
|
||||||
|
cwd=PROJECT_ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if r.returncode == 0 and r.stdout.strip():
|
||||||
|
return r.stdout.strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return "main"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Stage 1: Git pull (10%)
|
# Stage 1: Git pull
|
||||||
update_status["stage"] = "pulling"
|
update_status["stage"] = "pulling"
|
||||||
update_status["progress"] = 10
|
update_status["progress"] = 10
|
||||||
|
|
||||||
# Пробуем pull с публичным доступом
|
git_env = {**os.environ, "GIT_TERMINAL_PROMPT": "0"}
|
||||||
|
branch = _detect_branch()
|
||||||
|
|
||||||
pull_result = subprocess.run(
|
pull_result = subprocess.run(
|
||||||
["git", "pull", "origin", "main"],
|
["git", "pull", "origin", branch],
|
||||||
cwd=PROJECT_ROOT,
|
cwd=PROJECT_ROOT,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=120,
|
timeout=120,
|
||||||
env={**os.environ, "GIT_TERMINAL_PROMPT": "0"} # Отключить запрос пароля
|
env=git_env,
|
||||||
)
|
)
|
||||||
|
logger.info(f"git pull stdout: {pull_result.stdout}")
|
||||||
|
logger.info(f"git pull stderr: {pull_result.stderr}")
|
||||||
|
|
||||||
if pull_result.returncode != 0:
|
if pull_result.returncode != 0:
|
||||||
# Если не получилось через pull, пробуем через reset
|
# Fallback: fetch + reset --hard
|
||||||
# Fetch с установкой remote для публичного доступа
|
|
||||||
fetch_result = subprocess.run(
|
fetch_result = subprocess.run(
|
||||||
["git", "fetch", "origin", "main"],
|
["git", "fetch", "origin", branch],
|
||||||
cwd=PROJECT_ROOT,
|
cwd=PROJECT_ROOT,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=120,
|
timeout=120,
|
||||||
env={**os.environ, "GIT_TERMINAL_PROMPT": "0"}
|
env=git_env,
|
||||||
)
|
)
|
||||||
|
|
||||||
if fetch_result.returncode != 0:
|
if fetch_result.returncode != 0:
|
||||||
raise Exception(f"Git fetch failed: {fetch_result.stderr}")
|
raise Exception(f"Git fetch failed: {fetch_result.stderr}")
|
||||||
|
|
||||||
# Reset к удаленной версии
|
|
||||||
reset_result = subprocess.run(
|
reset_result = subprocess.run(
|
||||||
["git", "reset", "--hard", "origin/main"],
|
["git", "reset", "--hard", f"origin/{branch}"],
|
||||||
cwd=PROJECT_ROOT,
|
cwd=PROJECT_ROOT,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=30
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
if reset_result.returncode != 0:
|
if reset_result.returncode != 0:
|
||||||
raise Exception(f"Git reset failed: {reset_result.stderr}")
|
raise Exception(f"Git reset failed: {reset_result.stderr}")
|
||||||
|
|
||||||
# Stage 2: Docker build (50%)
|
# Stage 2: Docker rebuild
|
||||||
update_status["stage"] = "building"
|
update_status["stage"] = "building"
|
||||||
update_status["progress"] = 30
|
update_status["progress"] = 30
|
||||||
|
|
||||||
# Запускаем rebuild в фоне - это перезапустит сам контейнер
|
# Detect which compose command is available
|
||||||
# Используем nohup чтобы процесс продолжился после рестарта
|
compose_cmd = "docker compose"
|
||||||
build_script = f"""
|
try:
|
||||||
|
check = subprocess.run(
|
||||||
|
["docker", "compose", "version"],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if check.returncode != 0:
|
||||||
|
compose_cmd = "docker-compose"
|
||||||
|
except Exception:
|
||||||
|
compose_cmd = "docker-compose"
|
||||||
|
|
||||||
|
# Script runs after this process exits — writes logs to persistent volume
|
||||||
|
build_script = f"""#!/bin/sh
|
||||||
|
set -e
|
||||||
cd {PROJECT_ROOT}
|
cd {PROJECT_ROOT}
|
||||||
docker compose up -d --build app 2>&1 | tee /tmp/update.log
|
echo "[update] Starting docker rebuild at $(date)" >> {UPDATE_LOG}
|
||||||
|
{compose_cmd} up -d --build app >> {UPDATE_LOG} 2>&1
|
||||||
|
echo "[update] Docker rebuild finished at $(date)" >> {UPDATE_LOG}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Записываем скрипт обновления
|
|
||||||
script_path = "/tmp/update_panel.sh"
|
script_path = "/tmp/update_panel.sh"
|
||||||
with open(script_path, "w") as f:
|
with open(script_path, "w") as f:
|
||||||
f.write(build_script)
|
f.write(build_script)
|
||||||
os.chmod(script_path, 0o755)
|
os.chmod(script_path, 0o755)
|
||||||
|
|
||||||
# Запускаем скрипт через nohup
|
with open(UPDATE_LOG, "a") as f:
|
||||||
|
f.write(f"[update] git pull done (branch={branch}), launching rebuild...\n")
|
||||||
|
|
||||||
|
# Launch detached so it survives container restart
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
["nohup", "sh", script_path],
|
["nohup", "sh", script_path],
|
||||||
cwd=PROJECT_ROOT,
|
cwd=PROJECT_ROOT,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
start_new_session=True
|
start_new_session=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
update_status["stage"] = "restarting"
|
update_status["stage"] = "restarting"
|
||||||
update_status["progress"] = 80
|
update_status["progress"] = 80
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "Update started. The panel will restart shortly."
|
"message": "Update started. The panel will restart shortly.",
|
||||||
}
|
}
|
||||||
|
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
update_status["error"] = "Update timeout"
|
update_status["error"] = "Update timeout"
|
||||||
update_status["is_updating"] = False
|
update_status["is_updating"] = False
|
||||||
|
|||||||
Reference in New Issue
Block a user