#!/bin/bash set -euo pipefail script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" root_dir="$(cd -- "$script_dir/.." && pwd)" local_folder="${LOCAL_FOLDER:-$root_dir}" local_env_file="${LOCAL_ENV_FILE:-$local_folder/.env}" remote_folder="${REMOTE_FOLDER:-/opt/www/virtual/SkinbaseNova}" remote_server="${REMOTE_SERVER:-klevze@server3.klevze.si}" remote_env_file="${REMOTE_ENV_FILE:-$remote_folder/.env}" php_bin="${PHP_BIN:-php}" ssh_bin="${SSH_BIN:-ssh}" scp_bin="${SCP_BIN:-scp}" local_mysqldump_command="${LOCAL_MYSQLDUMP_COMMAND:-}" force_replace=0 skip_remote_backup=0 run_remote_migrate=1 usage() { cat <<'EOF' Usage: bash scripts/push-db-to-prod.sh --force [options] Options: --force Required. Confirms that production DB replacement is intentional. --skip-remote-backup Skip taking a production backup before import. --skip-migrate Skip php artisan migrate after import. --remote-server HOST Override the SSH destination. --remote-folder PATH Override the remote app path. --local-env-file PATH Override the local Laravel .env path. --remote-env-file PATH Override the remote Laravel .env path. --help Show this help. This script replaces the remote application database with a dump created from the local database. EOF } is_wsl() { [[ -n "${WSL_DISTRO_NAME:-}" || -n "${WSL_INTEROP:-}" ]] } use_windows_local_mysql_client() { if [[ -n "$local_mysqldump_command" ]]; then return 1 fi if ! is_wsl; then return 1 fi if [[ "$local_db_host" != "127.0.0.1" && "$local_db_host" != "localhost" ]]; then return 1 fi command -v powershell.exe >/dev/null 2>&1 && command -v wslpath >/dev/null 2>&1 } run_local_dump() { local uncompressed_dump_file="${local_dump_file%.gz}" if [[ -n "$local_mysqldump_command" ]]; then echo "Creating local database dump with LOCAL_MYSQLDUMP_COMMAND..." ( cd "$local_folder" eval "$local_mysqldump_command" ) | gzip > "$local_dump_file" return fi if use_windows_local_mysql_client; then local windows_dump_file local escaped_password local escaped_host local escaped_port local escaped_user local escaped_name windows_dump_file="$(wslpath -w "$uncompressed_dump_file")" escaped_password="${local_db_password//\'/\'\'}" escaped_host="${local_db_host//\'/\'\'}" escaped_port="${local_db_port//\'/\'\'}" escaped_user="${local_db_user//\'/\'\'}" escaped_name="${local_db_name//\'/\'\'}" echo "Detected WSL with a Windows-local MySQL host; running local dump with Windows mysqldump.exe..." powershell.exe -NoProfile -ExecutionPolicy Bypass -Command \ "\$ErrorActionPreference = 'Stop'; \ \$mysqldump = (Get-Command mysqldump.exe).Source; \ if (-not \$mysqldump) { throw 'mysqldump.exe not found on Windows PATH.' } \ \$env:MYSQL_PWD = '$escaped_password'; \ \$arguments = @('--host=$escaped_host', '--port=$escaped_port', '--user=$escaped_user', '--single-transaction', '--quick', '--routines', '--triggers', '--hex-blob', '--no-tablespaces', '--default-character-set=utf8mb4', '$escaped_name'); \ \$process = Start-Process -FilePath \$mysqldump -ArgumentList \$arguments -NoNewWindow -RedirectStandardOutput '$windows_dump_file' -Wait -PassThru; \ if (\$process.ExitCode -ne 0) { exit \$process.ExitCode }" gzip -f "$uncompressed_dump_file" return fi echo "Creating local database dump from $local_db_name..." MYSQL_PWD="$local_db_password" mysqldump \ --host="$local_db_host" \ --port="$local_db_port" \ --user="$local_db_user" \ --single-transaction \ --quick \ --routines \ --triggers \ --hex-blob \ --no-tablespaces \ --default-character-set=utf8mb4 \ "$local_db_name" | gzip > "$local_dump_file" } read_env_value() { local key="$1" local file="$2" local line line="$(grep -E "^${key}=" "$file" | tail -n 1 || true)" line="${line#*=}" line="${line%$'\r'}" line="${line#\"}" line="${line%\"}" line="${line#\'}" line="${line%\'}" printf '%s' "$line" } while [[ $# -gt 0 ]]; do case "$1" in --force) force_replace=1 ;; --skip-remote-backup) skip_remote_backup=1 ;; --skip-migrate) run_remote_migrate=0 ;; --remote-server) shift remote_server="${1:?Missing value for --remote-server}" ;; --remote-folder) shift remote_folder="${1:?Missing value for --remote-folder}" remote_env_file="$remote_folder/.env" ;; --local-env-file) shift local_env_file="${1:?Missing value for --local-env-file}" ;; --remote-env-file) shift remote_env_file="${1:?Missing value for --remote-env-file}" ;; --help|-h) usage exit 0 ;; *) echo "Unknown option: $1" >&2 usage >&2 exit 1 ;; esac shift done if [[ "$force_replace" -ne 1 ]]; then echo "Refusing to replace the production database without --force." >&2 exit 1 fi if [[ ! -f "$local_env_file" ]]; then echo "Local env file not found: $local_env_file" >&2 exit 1 fi local_db_host="$(read_env_value DB_HOST "$local_env_file")" local_db_port="$(read_env_value DB_PORT "$local_env_file")" local_db_name="$(read_env_value DB_DATABASE "$local_env_file")" local_db_user="$(read_env_value DB_USERNAME "$local_env_file")" local_db_password="$(read_env_value DB_PASSWORD "$local_env_file")" if [[ -z "$local_db_host" || -z "$local_db_port" || -z "$local_db_name" || -z "$local_db_user" ]]; then echo "Local database settings are incomplete in $local_env_file" >&2 exit 1 fi local_dump_file="$(mktemp "${TMPDIR:-/tmp}/skinbase-prod-sync-XXXXXX.sql.gz")" remote_dump_file="/tmp/$(basename "$local_dump_file")" cleanup() { rm -f "$local_dump_file" rm -f "${local_dump_file%.gz}" } trap cleanup EXIT run_local_dump echo "Uploading dump to $remote_server..." "$scp_bin" "$local_dump_file" "$remote_server:$remote_dump_file" echo "Importing dump on the production server..." "$ssh_bin" "$remote_server" \ REMOTE_FOLDER="$(printf '%q' "$remote_folder")" \ REMOTE_ENV_FILE="$(printf '%q' "$remote_env_file")" \ REMOTE_DUMP_FILE="$(printf '%q' "$remote_dump_file")" \ PHP_BIN="$(printf '%q' "$php_bin")" \ SKIP_REMOTE_BACKUP="$skip_remote_backup" \ RUN_REMOTE_MIGRATE="$run_remote_migrate" \ 'bash -s' <<'EOF' set -euo pipefail read_env_value() { local key="$1" local file="$2" local line line="$(grep -E "^${key}=" "$file" | tail -n 1 || true)" line="${line#*=}" line="${line%$'\r'}" line="${line#\"}" line="${line%\"}" line="${line#\'}" line="${line%\'}" printf '%s' "$line" } if [[ ! -f "$REMOTE_ENV_FILE" ]]; then echo "Remote env file not found: $REMOTE_ENV_FILE" >&2 exit 1 fi db_host="$(read_env_value DB_HOST "$REMOTE_ENV_FILE")" db_port="$(read_env_value DB_PORT "$REMOTE_ENV_FILE")" db_name="$(read_env_value DB_DATABASE "$REMOTE_ENV_FILE")" db_user="$(read_env_value DB_USERNAME "$REMOTE_ENV_FILE")" db_password="$(read_env_value DB_PASSWORD "$REMOTE_ENV_FILE")" if [[ -z "$db_host" || -z "$db_port" || -z "$db_name" || -z "$db_user" ]]; then echo "Remote database settings are incomplete in $REMOTE_ENV_FILE" >&2 exit 1 fi if [[ "$SKIP_REMOTE_BACKUP" -eq 0 ]]; then backup_dir="$REMOTE_FOLDER/storage/app/deploy-backups" backup_file="$backup_dir/prod-before-sync-$(date +%Y%m%d-%H%M%S).sql.gz" mkdir -p "$backup_dir" echo "Creating production backup at $backup_file..." MYSQL_PWD="$db_password" mysqldump \ --host="$db_host" \ --port="$db_port" \ --user="$db_user" \ --single-transaction \ --quick \ --routines \ --triggers \ --hex-blob \ --no-tablespaces \ --default-character-set=utf8mb4 \ "$db_name" | gzip > "$backup_file" fi echo "Replacing production database $db_name..." gunzip -c "$REMOTE_DUMP_FILE" | MYSQL_PWD="$db_password" mysql \ --host="$db_host" \ --port="$db_port" \ --user="$db_user" \ "$db_name" rm -f "$REMOTE_DUMP_FILE" if [[ "$RUN_REMOTE_MIGRATE" -eq 1 ]]; then cd "$REMOTE_FOLDER" "$PHP_BIN" artisan migrate --force fi EOF echo "Production database sync complete."