2 Commits

Author SHA1 Message Date
7f439aa08e fix(updates): build before down so a failed rebuild keeps the stack up
All checks were successful
Backend Tests / pytest (Python 3.12) (push) Successful in 46s
Deploy to Production / Trigger production update (push) Successful in 8s
The host watchdog ran `down` before `build --no-cache`, so a failed
build left the whole stack offline ("code updated, containers don't come
up"). Reorder to build first (app + frontend) while the old stack runs;
only tear down once images are ready. Use `up -d --wait` so success means
containers actually became healthy (DB migrations run on app startup),
and log `compose ps` + a hint on failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:23 +05:00
11d2c06fb1 fix(updates): parse version build suffix like 1.5.2-1
_parse_version split on "." only, so "2-1" hit int() and the whole
version collapsed to (0,) — update checks ignored build bumps and could
even offer a "downgrade". Extract every numeric run via regex instead:
"1.5.2-1" -> (1, 5, 2, 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:23 +05:00
2 changed files with 39 additions and 24 deletions

View File

@@ -2,6 +2,7 @@
Update Service - Check for updates and perform system updates
"""
import os
import re
import subprocess
import asyncio
import logging
@@ -182,11 +183,14 @@ def parse_repo_url(url: str) -> tuple:
def _parse_version(v: str) -> tuple:
"""Parse version string into a tuple of ints for comparison, e.g. '1.0.3' -> (1, 0, 3)"""
try:
return tuple(int(x) for x in v.strip().split("."))
except Exception:
return (0,)
"""Parse version string into a tuple of ints for comparison.
Handles a build suffix after '-': '1.5.2-1' -> (1, 5, 2, 1), '1.0.3' -> (1, 0, 3).
Splitting on '.' alone breaks on '2-1' (int() raises), collapsing the whole
version to (0,); extracting every numeric run keeps build bumps comparable.
"""
nums = re.findall(r"\d+", v.strip())
return tuple(int(x) for x in nums) if nums else (0,)
def _is_newer(remote: str, local: str) -> bool:

View File

@@ -5,8 +5,12 @@
#
# Алгоритм:
# 1. Каждые 3 секунды проверяет наличие файла .update_trigger в PROJECT_DIR
# 2. При обнаружении: удаляет триггер → git pull → docker compose down →
# docker compose build --no-cache app → docker compose up -d → пишет лог
# 2. При обнаружении: удаляет триггер → git pull → СНАЧАЛА build (стек ещё
# работает) → docker compose down → docker compose up -d --wait → пишет лог
#
# Важно: build выполняется ДО down. Если сборка упадёт, старый стек продолжает
# работать, а не остаётся погашенным. up запускается с --wait, чтобы убедиться,
# что контейнеры реально стали healthy (миграции БД накатываются на старте app).
set -euo pipefail
@@ -77,30 +81,37 @@ while true; do
log "[1/4] git reset --hard OK"
fi
# ── Step 2: docker compose down ───────────────────────────────────────
log "[2/4] docker compose down..."
$COMPOSE_CMD down >> "$LOG_FILE" 2>&1 && log "[2/4] docker compose down OK" \
|| log "[2/4] WARNING: docker compose down returned non-zero (continuing)"
# ── Step 3: docker compose build --no-cache app ───────────────────────
log "[3/4] docker compose build --no-cache app..."
if $COMPOSE_CMD build --no-cache app >> "$LOG_FILE" 2>&1; then
log "[3/4] build OK"
# ── Step 2: build НОВЫХ образов, пока старый стек ещё работает ─────────
# Собираем ДО down: если сборка упадёт (частая беда с --no-cache из-за
# сети), старый стек остаётся поднятым, а не лежит выключенным.
# Собираем и app, и frontend — иначе изменения фронта не доезжают.
log "[2/4] docker compose build --no-cache app frontend..."
if $COMPOSE_CMD build --no-cache app frontend >> "$LOG_FILE" 2>&1; then
log "[2/4] build OK"
else
log "[3/4] ERROR: build failed — aborting, containers NOT restarted"
log "[2/4] ERROR: build failed — стек НЕ трогаем, остаётся на старой версии"
continue
fi
# ── Step 4: docker compose up -d ──────────────────────────────────────
log "[4/4] docker compose up -d..."
if $COMPOSE_CMD up -d >> "$LOG_FILE" 2>&1; then
log "[4/4] docker compose up -d OK"
# ── Step 3: docker compose down ───────────────────────────────────────
# Быстрый: образы уже готовы, простой минимален.
log "[3/4] docker compose down..."
$COMPOSE_CMD down >> "$LOG_FILE" 2>&1 && log "[3/4] docker compose down OK" \
|| log "[3/4] WARNING: docker compose down returned non-zero (continuing)"
# ── Step 4: docker compose up -d --wait ───────────────────────────────
# --wait дожидается healthcheck'ов (app накатывает миграции БД на старте);
# без него up возвращает успех, даже если контейнер тут же упал.
log "[4/4] docker compose up -d --wait..."
if $COMPOSE_CMD up -d --wait --wait-timeout 180 >> "$LOG_FILE" 2>&1; then
log "[4/4] docker compose up -d OK (контейнеры healthy)"
log "=== Update completed successfully ==="
else
log "[4/4] ERROR: docker compose up -d failed"
log "[4/4] ERROR: контейнеры не стали healthy за 180с — состояние стека:"
$COMPOSE_CMD ps >> "$LOG_FILE" 2>&1 || true
log "[4/4] Проверьте логи app: '$COMPOSE_CMD logs --tail=100 app' (вероятно ошибка миграции/нового кода)"
continue
fi
log "=== Update completed successfully ==="
fi
sleep "$POLL_INTERVAL"