Improved MySQL migration system

no ref

- Currently we prompt a user for a username to dump their Ghost database with which can be confusing if users can't remember their database details
- The Ghost user _likely_ has the required permissions to dump the database itself so we can first try that without prompting the user and _then_ prompt them if it doesn't work
- We added `--no-tablespaces` to the dump command to try and reduce the likelyhood of hitting users not having the `PROCESS` permission since most hosted DBs don't let this and Ghost's DB setup doesn't require DBs be dumped with that anyway
This commit is contained in:
James Loh
2025-07-08 15:57:13 +10:00
parent fb8703d862
commit 6890f5c4c6
2 changed files with 126 additions and 25 deletions

View File

@@ -52,10 +52,17 @@ cleanup() {
if [[ $exit_code -ne 0 && -f "$RECOVERY_SCRIPT" ]]; then
echo ""
echo "ERROR: Migration failed!"
echo "════════════════════════════════════════════════════════════"
echo "❌ MIGRATION FAILED!"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "Don't worry - your data is safe!"
echo ""
echo "To restore your original Ghost installation, run:"
echo " bash $RECOVERY_SCRIPT"
echo ""
echo "Need help? Check the migration logs above for error details."
echo "════════════════════════════════════════════════════════════"
fi
exit $exit_code
@@ -66,6 +73,7 @@ trap cleanup EXIT INT TERM
# Create recovery script
create_recovery_script() {
local service_name="$1"
cat > "$RECOVERY_SCRIPT" << EOF
#!/usr/bin/env bash
# Recovery script generated by Ghost migration on $(date)
@@ -79,15 +87,19 @@ echo "Restoring original Ghost installation..."
docker compose down 2>/dev/null || true
# Re-enable and start the original Ghost service
systemctl enable "${ghost_service_name}"
systemctl start "${ghost_service_name}"
echo "Original Ghost installation has been restored."
echo "You can check the status with: systemctl status ${ghost_service_name}"
if [[ -n "${service_name}" ]]; then
systemctl enable "${service_name}" 2>/dev/null || true
systemctl start "${service_name}" 2>/dev/null || true
echo "Original Ghost installation has been restored."
echo "You can check the status with: systemctl status ${service_name}"
else
echo "Note: Ghost service was not yet stopped, so no restoration needed."
echo "Your original installation should still be running."
fi
EOF
chmod +x "$RECOVERY_SCRIPT"
echo "Recovery script created at: $RECOVERY_SCRIPT"
echo "Recovery script created at: $RECOVERY_SCRIPT"
}
# Validate MySQL connection
@@ -188,6 +200,21 @@ migrate_content() {
echo "✓ Content migration completed"
}
# Test if we can dump database with given credentials
test_mysql_dump() {
local host=$1
local database=$2
local user=$3
local password=$4
# Try a minimal dump to test permissions
if mysqldump --no-tablespaces --no-data -h"$host" -u"$user" -p"$password" "$database" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
# Export and import database
migrate_database() {
local mysql_host
@@ -197,9 +224,38 @@ migrate_database() {
echo "Exporting database from $mysql_host..."
# Export database
if ! mysqldump -h"$mysql_host" -u"$mysql_user" -p"$mysql_password" "$mysql_database" > "$TEMP_SQL_FILE"; then
# Export database with proper error handling
local dump_output
local dump_status
dump_output=$(mysqldump --no-tablespaces -h"$mysql_host" -u"$mysql_user" -p"$mysql_password" "$mysql_database" 2>&1 > "$TEMP_SQL_FILE")
dump_status=$?
# Check for errors in output (mysqldump may return 0 even with some errors)
if [[ $dump_status -ne 0 ]] || [[ "$dump_output" =~ "Error:" ]]; then
echo ""
echo "ERROR: Failed to export database"
if [[ "$dump_output" =~ "PROCESS privilege" ]]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "The MySQL user '$mysql_user' needs the PROCESS privilege."
echo ""
echo "To fix this, connect to MySQL as a privileged user and run:"
echo " GRANT PROCESS ON *.* TO '$mysql_user'@'%';"
echo " FLUSH PRIVILEGES;"
echo ""
echo "Or retry with a user that has sufficient privileges (e.g., root)."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
elif [[ -n "$dump_output" ]]; then
echo "Error details: $dump_output"
fi
exit 1
fi
# Verify the dump file exists and has content
if [[ ! -f "$TEMP_SQL_FILE" ]] || [[ ! -s "$TEMP_SQL_FILE" ]]; then
echo ""
echo "ERROR: Database dump file is empty or missing"
echo "This might indicate insufficient disk space or permissions issues."
exit 1
fi
@@ -286,8 +342,12 @@ main() {
# Get database configuration
local mysql_host
local mysql_database
local ghost_mysql_user
local ghost_mysql_password
mysql_host=$(jq -r < "${current_location}/config.production.json" '.database.connection.host')
mysql_database=$(jq -r < "${current_location}/config.production.json" '.database.connection.database')
ghost_mysql_user=$(jq -r < "${current_location}/config.production.json" '.database.connection.user')
ghost_mysql_password=$(jq -r < "${current_location}/config.production.json" '.database.connection.password')
# Check disk space
echo ""
@@ -321,23 +381,52 @@ main() {
echo "✓ Disk space check passed"
echo ""
# Get MySQL credentials and validate
read -rp "MySQL user for database export (default: root): " mysql_user
mysql_user=${mysql_user:-root}
# Try Ghost's own credentials first
echo "Testing database export with Ghost's credentials..."
if test_mysql_dump "$mysql_host" "$mysql_database" "$ghost_mysql_user" "$ghost_mysql_password"; then
echo "✓ Ghost's credentials have sufficient privileges"
mysql_user="$ghost_mysql_user"
mysql_password="$ghost_mysql_password"
else
echo "Ghost's database user doesn't have sufficient privileges for export."
echo "Please provide credentials for a MySQL user with dump privileges."
echo ""
# Get password securely
echo -n "MySQL password for ${mysql_user}: "
read -rs mysql_password
echo ""
# Get MySQL credentials and validate
read -rp "MySQL user for database export (default: root): " mysql_user
mysql_user=${mysql_user:-root}
# Validate connection
if ! validate_mysql_connection "$mysql_host" "$mysql_database" "$mysql_user" "$mysql_password"; then
echo "Please check your MySQL credentials and try again."
exit 1
# Get password securely
echo -n "MySQL password for ${mysql_user}: "
read -rs mysql_password
echo ""
# Validate connection
if ! validate_mysql_connection "$mysql_host" "$mysql_database" "$mysql_user" "$mysql_password"; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Could not connect to MySQL database."
echo ""
echo "Please verify:"
echo " • MySQL service is running"
echo " • Credentials are correct"
echo " • User has access from this host"
echo " • Database name is correct"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
exit 1
fi
# Test dump permissions
if ! test_mysql_dump "$mysql_host" "$mysql_database" "$mysql_user" "$mysql_password"; then
echo ""
echo "ERROR: The provided user doesn't have sufficient privileges for database export."
echo "Please ensure the user has the necessary privileges or try a different user."
exit 1
fi
fi
# Create recovery script
create_recovery_script
# Create recovery script (with empty service name since we haven't stopped anything yet)
create_recovery_script ""
# Final confirmation before stopping Ghost
echo ""
@@ -365,8 +454,17 @@ main() {
# Stop Ghost service
echo ""
echo "Stopping Ghost service..."
systemctl stop "$ghost_service_name"
systemctl disable "$ghost_service_name"
# Update recovery script with actual service name before stopping
create_recovery_script "$ghost_service_name"
if ! systemctl stop "$ghost_service_name"; then
echo "ERROR: Failed to stop Ghost service"
echo "Please check: systemctl status $ghost_service_name"
exit 1
fi
systemctl disable "$ghost_service_name" 2>/dev/null || true
echo "✓ Ghost service stopped"
# Migrate content