225 lines
6.2 KiB
Bash
Executable File
225 lines
6.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
|
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
|
|
COMPOSE_FILE="${COMPOSE_FILE:-${ROOT_DIR}/compose.yaml}"
|
|
APP_SERVICE="${APP_SERVICE:-laravel.test}"
|
|
DB_SERVICE="${DB_SERVICE:-mysql}"
|
|
COMPOSER_IMAGE="${COMPOSER_IMAGE:-composer:2}"
|
|
BUILD_IMAGES=1
|
|
WARM_CACHE=1
|
|
PROJECT_UID=""
|
|
PROJECT_GID=""
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Использование:
|
|
./scripts/deploy-ubuntu.sh [--skip-build] [--skip-cache]
|
|
|
|
Что делает скрипт:
|
|
1. Создаёт .env из .env.example, если файла ещё нет
|
|
2. Подготавливает storage/bootstrap cache директории
|
|
3. Собирает docker-образ приложения
|
|
4. Устанавливает production-зависимости Composer внутри контейнера
|
|
5. Генерирует APP_KEY, если он пустой
|
|
6. Поднимает контейнеры
|
|
7. Ждёт готовности MySQL
|
|
8. Запускает миграции и прогревает Laravel-кэш
|
|
|
|
Требования:
|
|
- Ubuntu с установленными Docker Engine и Docker Compose Plugin
|
|
- настроенный .env с корректными значениями APP_URL и AZEROTHCORE_AUTH_DB_*
|
|
EOF
|
|
}
|
|
|
|
log() {
|
|
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
|
}
|
|
|
|
fail() {
|
|
printf '[error] %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_command() {
|
|
command -v "$1" >/dev/null 2>&1 || fail "Не найдена команда: $1"
|
|
}
|
|
|
|
env_value() {
|
|
local key="$1"
|
|
|
|
[[ -f "$ENV_FILE" ]] || return 0
|
|
|
|
grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- | sed -e 's/^"//' -e 's/"$//'
|
|
}
|
|
|
|
set_env_value() {
|
|
local key="$1"
|
|
local value="$2"
|
|
|
|
if grep -qE "^${key}=" "$ENV_FILE"; then
|
|
sed -i "s#^${key}=.*#${key}=${value}#" "$ENV_FILE"
|
|
else
|
|
printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE"
|
|
fi
|
|
}
|
|
|
|
compose() {
|
|
docker compose -f "$COMPOSE_FILE" "$@"
|
|
}
|
|
|
|
composer_install() {
|
|
docker run --rm \
|
|
-u "${PROJECT_UID}:${PROJECT_GID}" \
|
|
-e HOME=/tmp/composer-home \
|
|
-e COMPOSER_HOME=/tmp/composer-home \
|
|
-v "${ROOT_DIR}:/app" \
|
|
-w /app \
|
|
"$COMPOSER_IMAGE" \
|
|
sh -lc 'mkdir -p "$COMPOSER_HOME" /app/vendor && git config --global --add safe.directory /app && composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction'
|
|
}
|
|
|
|
app_exec() {
|
|
compose exec -T --user "${WWWUSER}:${WWWGROUP}" "$APP_SERVICE" "$@"
|
|
}
|
|
|
|
wait_for_mysql() {
|
|
local container_id
|
|
local status
|
|
local attempts=0
|
|
local max_attempts=60
|
|
|
|
container_id="$(compose ps -q "$DB_SERVICE")"
|
|
[[ -n "$container_id" ]] || fail "Не удалось определить контейнер MySQL"
|
|
|
|
while (( attempts < max_attempts )); do
|
|
status="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id" 2>/dev/null || true)"
|
|
|
|
if [[ "$status" == "healthy" || "$status" == "running" ]]; then
|
|
log "MySQL готов"
|
|
return 0
|
|
fi
|
|
|
|
attempts=$((attempts + 1))
|
|
sleep 2
|
|
done
|
|
|
|
fail "MySQL не перешёл в готовое состояние"
|
|
}
|
|
|
|
prepare_env() {
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
[[ -f "${ROOT_DIR}/.env.example" ]] || fail "Не найден .env.example"
|
|
|
|
cp "${ROOT_DIR}/.env.example" "$ENV_FILE"
|
|
set_env_value "APP_ENV" "production"
|
|
set_env_value "APP_DEBUG" "false"
|
|
|
|
log "Создан ${ENV_FILE} из .env.example"
|
|
log "Проверь APP_URL и AZEROTHCORE_AUTH_DB_* перед публичным запуском"
|
|
}
|
|
|
|
prepare_directories() {
|
|
mkdir -p \
|
|
"${ROOT_DIR}/bootstrap/cache" \
|
|
"${ROOT_DIR}/storage/app" \
|
|
"${ROOT_DIR}/storage/framework/cache/data" \
|
|
"${ROOT_DIR}/storage/framework/sessions" \
|
|
"${ROOT_DIR}/storage/framework/testing" \
|
|
"${ROOT_DIR}/storage/framework/views" \
|
|
"${ROOT_DIR}/storage/logs" \
|
|
"${ROOT_DIR}/vendor"
|
|
|
|
chmod -R ug+rwx "${ROOT_DIR}/bootstrap/cache" "${ROOT_DIR}/storage"
|
|
|
|
if [[ "$(id -u)" -eq 0 ]]; then
|
|
chown -R "${WWWUSER}:${WWWGROUP}" "${ROOT_DIR}/bootstrap/cache" "${ROOT_DIR}/storage"
|
|
fi
|
|
}
|
|
|
|
resolve_ids() {
|
|
PROJECT_UID="$(stat -c '%u' "$ROOT_DIR")"
|
|
PROJECT_GID="$(stat -c '%g' "$ROOT_DIR")"
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--skip-build)
|
|
BUILD_IMAGES=0
|
|
;;
|
|
--skip-cache)
|
|
WARM_CACHE=0
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
fail "Неизвестный аргумент: $1"
|
|
;;
|
|
esac
|
|
|
|
shift
|
|
done
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
require_command docker
|
|
docker compose version >/dev/null 2>&1 || fail "Не найден Docker Compose Plugin"
|
|
|
|
cd "$ROOT_DIR"
|
|
|
|
prepare_env
|
|
|
|
WWWUSER="$(env_value WWWUSER)"
|
|
WWWGROUP="$(env_value WWWGROUP)"
|
|
WWWUSER="${WWWUSER:-1000}"
|
|
WWWGROUP="${WWWGROUP:-1000}"
|
|
resolve_ids
|
|
prepare_directories
|
|
|
|
log "Устанавливаю production-зависимости Composer"
|
|
composer_install
|
|
|
|
if (( BUILD_IMAGES )); then
|
|
log "Собираю образ приложения"
|
|
compose build "$APP_SERVICE"
|
|
fi
|
|
|
|
if [[ -z "$(env_value APP_KEY)" ]]; then
|
|
log "Генерирую APP_KEY"
|
|
compose run --rm --no-deps --user "${WWWUSER}:${WWWGROUP}" --entrypoint php "$APP_SERVICE" artisan key:generate --force
|
|
fi
|
|
|
|
log "Поднимаю контейнеры"
|
|
compose up -d
|
|
|
|
wait_for_mysql
|
|
|
|
log "Выполняю миграции"
|
|
app_exec php artisan migrate --force
|
|
|
|
if (( WARM_CACHE )); then
|
|
log "Обновляю Laravel-кэш"
|
|
app_exec php artisan optimize:clear
|
|
app_exec php artisan config:cache
|
|
app_exec php artisan route:cache
|
|
app_exec php artisan view:cache
|
|
fi
|
|
|
|
log "Деплой завершён"
|
|
log "Сайт доступен по адресу: $(env_value APP_URL)"
|
|
log "Проверка контейнеров: docker compose -f ${COMPOSE_FILE} ps"
|
|
}
|
|
|
|
main "$@"
|