This commit is contained in:
2026-10-04 14:33:33 +04:00
parent 6c2b76c4ca
commit b9f2a6da1e
4 changed files with 232 additions and 3 deletions
+92 -3
View File
@@ -8,6 +8,8 @@ PROD_COMPOSE="${ROOT_DIR}/compose.prod.yaml"
DEV_COMPOSE="${ROOT_DIR}/compose.yaml"
ENV_FILE="${ROOT_DIR}/.env"
LEGACY_WAS_RUNNING=0
PROD_COMPOSE_ARGS=(-f "$PROD_COMPOSE")
SSH_TUNNEL=0
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
@@ -19,12 +21,42 @@ fail() {
}
env_value() {
local key="$1"
grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- | sed -e 's/^"//' -e 's/"$//'
local key="$1" line value
line="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 || true)"
value="${line#*=}"
case "$value" in
\"*\") value="${value:1:${#value}-2}" ;;
\'*\') value="${value:1:${#value}-2}" ;;
esac
printf '%s\n' "$value"
}
configure_compose() {
local auth_host characters_host arg
for arg in "$@"; do
case "$arg" in
--ssh-tunnel) SSH_TUNNEL=1 ;;
*) fail "Неизвестный параметр: $arg (доступен --ssh-tunnel)" ;;
esac
done
auth_host="$(env_value VMANGOS_AUTH_DB_HOST)"
characters_host="$(env_value VMANGOS_CHARACTERS_DB_HOST)"
if [[ "$auth_host" == core-db-tunnel || "$characters_host" == core-db-tunnel ]]; then
SSH_TUNNEL=1
fi
if (( SSH_TUNNEL )); then
[[ -f "${ROOT_DIR}/compose.prod.ssh-tunnel.yaml" ]] || fail "Не найден compose.prod.ssh-tunnel.yaml"
if [[ -n "$(env_value VMANGOS_AUTH_DB_URL)" || -n "$(env_value VMANGOS_CHARACTERS_DB_URL)" ]]; then
fail "Для SSH-туннеля уберите VMANGOS_AUTH_DB_URL и VMANGOS_CHARACTERS_DB_URL; используйте отдельные настройки БД"
fi
PROD_COMPOSE_ARGS+=(-f "${ROOT_DIR}/compose.prod.ssh-tunnel.yaml")
log "БД ядра: SSH-туннель core-db-tunnel:3306"
fi
prod config --quiet
}
prod() {
docker compose -f "$PROD_COMPOSE" "$@"
docker compose "${PROD_COMPOSE_ARGS[@]}" "$@"
}
legacy() {
@@ -63,12 +95,50 @@ rollback_legacy() {
fi
}
verify_frontend_assets() {
local app_port="$1"
local app_manifest web_manifest assets asset_path content_type
app_manifest="$(prod exec -T app sha256sum /var/www/html/public/build/manifest.json)" || return 1
web_manifest="$(prod exec -T web sha256sum /var/www/html/public/build/manifest.json)" || return 1
if [[ "$app_manifest" != "$web_manifest" ]]; then
log "Сборки frontend в app и web не совпадают"
return 1
fi
assets="$(prod exec -T app php -r '
$manifest = json_decode(file_get_contents("public/build/manifest.json"), true, 512, JSON_THROW_ON_ERROR);
$entry = $manifest["resources/js/app.js"] ?? [];
if (! isset($entry["file"])) {
fwrite(STDERR, "Missing JavaScript entry in Vite manifest\n");
exit(1);
}
foreach (array_merge([$entry["file"]], $entry["css"] ?? []) as $asset) {
echo "/build/".$asset."\n";
}
')" || return 1
while IFS= read -r asset_path; do
content_type="$(curl --fail --silent --show-error --max-time 15 \
--output /dev/null --write-out '%{content_type}' \
"http://127.0.0.1:${app_port}${asset_path}")" || return 1
case "${asset_path}:${content_type}" in
*.js:application/javascript*|*.js:text/javascript*|*.css:text/css*) ;;
*)
log "Некорректный Content-Type для ${asset_path}: ${content_type}"
return 1
;;
esac
done <<< "$assets"
}
main() {
command -v docker >/dev/null 2>&1 || fail "Не найден Docker"
docker compose version >/dev/null 2>&1 || fail "Не найден Docker Compose Plugin"
[[ -f "$ENV_FILE" ]] || fail "Не найден ${ENV_FILE}"
cd "$ROOT_DIR"
configure_compose "$@"
local app_uid app_gid app_port legacy_id
app_uid="$(env_value WWWUSER)"
@@ -86,15 +156,28 @@ main() {
log "Собираю production-образы"
prod build app web
if (( SSH_TUNNEL )); then
prod build core-db-tunnel
fi
log "Запускаю MySQL сайта"
prod up -d mysql
wait_for_health mysql
if (( SSH_TUNNEL )); then
log "Запускаю SSH-туннель к БД ядра"
prod up -d --no-deps core-db-tunnel
wait_for_health core-db-tunnel
fi
log "Запускаю новый PHP-FPM"
prod up -d --no-deps app
wait_for_health app
log "Проверяю доступ приложения к обеим БД ядра"
prod exec -T --user "${app_uid}:${app_gid}" app php artisan config:clear
prod exec -T --user "${app_uid}:${app_gid}" app php artisan tinker --execute='foreach (["vmangos_auth", "vmangos_characters"] as $name) { DB::connection($name)->select("SELECT 1"); echo $name." OK\n"; }'
log "Выполняю миграции и прогреваю кэш"
prod exec -T --user "${app_uid}:${app_gid}" app php artisan migrate --force
prod exec -T --user "${app_uid}:${app_gid}" app php artisan optimize
@@ -117,6 +200,12 @@ main() {
fail "HTTP-проверка production web завершилась ошибкой"
fi
log "Проверяю соответствие сборок frontend и доступность JavaScript/CSS"
if ! verify_frontend_assets "$app_port"; then
rollback_legacy
fail "Проверка frontend завершилась ошибкой"
fi
log "Production-деплой завершён: app и web healthy"
prod ps app web
}